博客这种服务的区别于论坛和所谓文集网站,很大程度上我认为是由于pingback/trackback的存在,使得博客这种自媒体有可以延展加入SNS的要素。所以分析博客程序,我们需要了解这种协议以及协议的实施细节。
 在dottext的源代码中,在发表作品中,我们可以看到有pingback协议的支持,同时在web services的实现中,有trackback协议的实现。至于什么是piongback/trackback协议,google下应当可以找到,也不用我费口舌。
 通过              <HttpHandlerpattern="/(?:admin)"type="Dottext.Web.UI.Handlers.BlogExistingPageHandler, Dottext.Web"handlerType="Factory"/>
 的映射,使得我们访问每一个blog的admin目录时候,都会UrlRewrite到dottexweb\admin目录下的相对应aspx文件(参考前面部分),其中在发表post的时候,我们看到是这样一个调用关系:
 private void UpdatePost()
          {    
               if(Page.IsValid)
               {
                    string successMessage = Constants.RES_SUCCESSNEW;
                    try
                    {
                        Entry entry = new Entry(EntryType);
                          entry.Title = txbTitle.Text;
                         entry.Body = Globals.StripRTB(ftbBody.Text,Request.Url.Host);
                        …
                         entry.BlogID = Config.CurrentBlog(Context).BlogID;
                        
                        if (PostID > 0)
                        {//是更新操作
                             successMessage = Constants.RES_SUCCESSEDIT;
                             entry.DateUpdated = DateTime.Now;//BlogTime.CurrentBloggerTime;
                             entry.EntryID = PostID;
                             …
                             Entries.Update(entry);
                             …
                        }
                        else
                        {//新建操作
                             entry.DateCreated = DateTime.Now;//BlogTime.CurrentBloggerTime;
                             PostID = Entries.Create(entry);        
                        }
 …
                    }
                    catch(Exception ex)
                    {…           }
                    finally
                    {    …        }
               }
      }
      Entries.Create(entry);是这样的:
          public static int Create(Entry entry, int[] CategoryIDs)
          {
               HandlerManager.PreCommit(entry,ProcessAction.Insert);             
               int result = DTOProvider.Instance().Create(entry,CategoryIDs);              
               if(result > 0)
               {
                    HandlerManager.PostCommit(entry,ProcessAction.Insert);                 
               }
               return result;
      }
      最终的数据存储试调用DTOProvider也就是DataDTOProvider 最终是落到 SqlDataProvider 来实现数据存储操作。但是我们注意到 HandlerManager.PostCommit(entry,ProcessAction.Insert);     这个操作。仔细看看:
      HandlerManager 是一个关于Entry操作类的包装类(wapper class),PreCommit是这样定义的:
           Process(ProcessState.PreCommit,e,pa);
      而Process是这样读取web.config的
      public static void Process(ProcessState ps, Entry e, ProcessAction pa)
          {     //Do we have factories? 在疑惑是否该用工厂模式呢
               EntryHandler[] hanlers = Config.Settings.EntryHandlers;     //这是反序列化哦,这里的Config是Dottext.Framework.Configuration.Config 
               if(e != null && hanlers != null)
               {     //walk the entries 遍历全部处理例程
                    for(int i = 0; i<hanlers.Length; i++)
                    {
                         EntryHandler handler = hanlers[i];
                         if(ShouldProcess(ps,handler,e.PostType,pa))
                        {
                             IEntryFactoryHandler ihandler = handler.IEntryFactoryHandlerInstance;                            
                             //Call the IEntryFactoryHandler configure method. This gives async items a chance to "ready" themselves 
                             //before leaving the main thread and entering the managed queue.
                             ihandler.Configure();
                             if(handler.IsAsync)
                             {//Add factory to managed queue.
                                  EntryHanlderQueue.Enqueue(ihandler,e);
                             }
                             else
                             {
                                  ihandler.Process(e);
                             }
                        }
                        
                    }
               }
          }
 ShouldProcess 是判断是预提交还是已经提交post,决定是否应该进行handler的实例化,如果是已经提交的Post,我们需要进行handler.IEntryFactoryHandlerInstance;      IentryFactoryHandlerInstance最终是通过
 ihandler = (IEntryFactoryHandler)Activator.CreateInstance(Type.GetType(this.ItemType));
 来实例化数组元素的().
 经过实例化后,就可以执行了。此时根据 handler.IsAsync 的属性,决定是允许 EntryHanlderQueue.Enqueue(ihandler,e); 加入队列,还是马上处理
 ihandler.Process(e);.
 对于可以异步执行的静态函数 Enque 处理:
 public static void Enqueue(IEntryFactoryHandler factory, Entry e)
          {
               EntryHanlderQueue ehq = new EntryHanlderQueue(factory,e);
               ManagedThreadPool.QueueUserWorkItem(new WaitCallback(ehq.Enqueue));
      }
 构造一个实例,然后加入线程队列进行任务排队。线程管理暂不讨论。我们看看这几个EntryHandler.
 TrackBack Handler是如何处理的呢?
 public void Process(Dottext.Framework.Components.Entry e)
          {
               //Get a list of links from the current post
               StringCollection links = TrackHelpers.GetLinks(e.Body);
               if(links != null && links.Count > 0)
               {
                    //Instantiate our proxy
                    TrackBackNotificationProxy proxy = new TrackBackNotificationProxy();
                    
                    //Walk the links
                    for(int i = 0; i<links.Count; i++)
                    {
                        string link = links[i];
                        //get the page text
                        string pageText = BlogRequest.GetPageText(link,e.Link);
                         if(pageText != null)
                        {
                             try
                             {
                                  string desc = null;
                                  if(e.HasDescription)
                                  {
                                      desc = e.Description;
                                  }
                                  else
                                  {
                                       desc=string.Format("TrackBack From:{0}",e.Link);
                                       
                                  }    
 desc = regexStripHTML.Replace(e.Body,string.Empty);
                                       if(desc.Length > 100)
                                      {
                                           int place = 100;
                                           int len = desc.Length-1;
                                           while(!Char.IsWhiteSpace(desc[place]) && i < len)
                                           {
                                                place++;
                                           }
                                           desc = string.Format("{0}...",desc.Substring(0,place));
                                      }
                                  }
                                  //attempt a trackback.
                             proxy.TrackBackPing(pageText,link,e.Title,e.Link,e.Author,desc);                            
                             }
                             catch(Exception ex)
                             {                                 Logger.LogManager.CreateExceptionLog(ex,string.Format("Trackback Failure: {0}",link));
                             }
                        }
                    }
               }
      }
  TrackHelpers.GetLinks 会分析Entry.Body字符串,获得post的全部href连结,也就是对外引用部分,这个TrackBack利用proxy.TrackBackPing(pageText,link,e.Title,e.Link,e.Author,desc); 将本文的对外引用通告刚刚获得的连接地址。
 TrackBackPing :
     string pageText = BlogRequest.GetPageText(link,e.Link);会利用BlogRequest的http协议能力下载被引用地址的source code,然后 link为另外blog的地址,而e.Link为reffer,这是为了告知对方那个页面引用了link。经过安全解码后,获得了link的源代码,然后TrackBackPing会进行分析,找寻string sPattern = @"<rdf:\w+\s[^>]*?>(</rdf:rdf>)?";匹配的部分,分析出其中的引用通告地址。下一步就是利用SendPing(string trackBackItem, string parameters),向目标地址处post一个application/x-www-form-urlencoded"的数据。此即完成了一次trackBack.
 
    其他几个EntryHandler也是分同步和异步的,大家可以照此阅读。
      
     题外话:那些没有礼貌的实现pingback/Trackback的所谓blog,就不要妄自称自己为博客服务商(BSP)吧。
  CNBlogsDottext10Beta2版本中,TRACKBACK功能被屏蔽掉了,原因可能是因为很多人安装成功后,在提交包含引用链接的POSTS时,出现错误: 
  将截断字符串或二进制数据 
 其实这是因为发送TRACKBACK的关键方法:SendPing(string trackBackItem, string parameters)里,发送字节流时按照ASCII码的长度来发送,当PARAMETERS中包含中文时,就会出错,解决方法是转换成UTF-8发送,下面是我修改过后的代码: 
  
  private void SendPing(string trackBackItem, string parameters) 
  { 
    HttpWebRequest request = BlogRequest.CreateRequest(trackBackItem); 
   request.Method = "POST"; 
    request.ContentType = "application/x-www-form-urlencoded"; 
   request.KeepAlive = false; 
    byte [] buff = Encoding.GetEncoding("UTF-8").GetBytes(parameters);      
    request.ContentLength = buff.Length; 
    Stream reqStream = null; 
   try 
   { 
    reqStream = request.GetRequestStream(); 
     reqStream.Write(buff, 0, buff.Length); 
   } 
   catch(Exception e) 
   { 
    Logger.LogManager.CreateExceptionLog(e,"SendPing Exception"); 
   } 
   finally 
   { 
    reqStream.Close(); 
   } 
 首先我们来看一下是怎么发送TRACKBACK的:入口是Dottext.Framework.EntryHandling.Process 
 检查文章内容中是否已经包含了远程网页的链接,只有包含才能继续 从远程链接的网页下载HTML代码,如果没得到,说明不是合法链接,要返回 在得到的HTML代码中检查是否已经包含了本文的链接,有说明已经PING过了,要返回 在得到的HTML代码中根据TRACKBACK标准取到要TRACKBACK链接(链接包含在RDF为键值的被注释的XHTML代码中),从而完成了由网页链接到TRACKBACK链接的转化 发送(PING)TRACKBACK。 
再来看一下接收TRACKBACK的流程,入口是:Dottext.Framework.Tracking.TrackBackHandler.ProcessRequest 
 根据PING过来的TRACKBACK链接得到本地文章的ID号,得不到则不是合法链接,要返回 REQUEST方法是否是POST,不是要返回,这是TRACKBACK标准规定的。 根据ID号从库中检索数据,生成ENTRY对象 根据传过来的URL下载远程网页的HTML代码,如果没得到或得到的HTML中没有包含本地文章的链接,说明不是合法链接,要返回 从得到的HTML代码中分析出对方的页面标题,如果没有,要返回 生成一个新的ENTRY对象,并对其各个属性赋值,然后入库 
由此我们看到DOTTEXT发送TRACKBACK时效率是比较低的。原因是需要去下载远程的HTML,这将是一个非常耗时的工作,更不用说还要从很可能十分庞大的HTML代码中提取出TRACKBACK链接。 
 再者就是接收TRACKBACK时,并没有建立屏蔽机制。从而无法避免垃极广告的侵袭,也就是我们所说的SPAM COMMENT。 
 我想要解决这些问题,需要改变发送TRACKBACK的发送机制。 
 不再根据TRACKBACK标准去自动获取TRACKBACK,这样不仅效率极低,而且很多网站并不支持这个标准(比如 
www.blogchinese.com 就直接提供引用通告,而不是隐藏在网页中,哈哈)的话,就无法TRACKBACK成功了。我们就认为用户输入的就是合法的TRACKBACK链接。直接进行发送。 为了能让用户得到合法的TRACKBACK地址,在每一篇文章内容之后,都显示此文的TRACKBACK链接。 再提供一个页面,输入网页链接,就能显示出此网页的TRACKBACK链接,以继续支持那些符合标准的网站 
在接收TRACKBACK时,我们相应做以下改动: 
 将对方URL拿到库里去验证,看对方是否已经PING过了,因为是在本地进行,速度会非常快。 在库中建立BLACKIP表,对来方的IP进行校验,这样就拥有了封对方IP的功能。 
以上只是我的设想,因为时间的原因,还没有动手去实现,如果大家有更好的建议,可以一起来探讨。