让阳光指引生命的每一刻,乘着风,跟随我融化空气

美丽的地球,有美丽的cnblog [hjp3]hjptype=song&player=1&son=http://www.taihugolf.com.cn/villa/sound_00ok01.mp3&autoplay=yes&autoreplay=1&bgcolor=FFFFFF&width=200&height=20[/hjp3]

Handler为web页面呈现资源程序集里的资源并防止非法盗链!


准备知识:

1。
    为了在web页面上呈现DotNET资源程序集里的任意资源,我们需要编写一个自定义的Handler程序,用这个Handler来接管处理特定扩展名的资源。DotNET Framework 提供了一个IHttpHandler接口,我们只要继承此接口,然后实现它提供的两个方法即可。

2。
    为了显示各种不同的Web资源,我们需要封装一个资源相关的类,用它来从资源程序集提取指定名称的资源,资源程序集内部的各种资源都是以嵌入的方式编译的,也就是说将图片,文字,音视频都嵌入到一个单独的DotNET Dll里,我们把它编译成一个类库,也就是DotNET资源程序集。如果你的资源程序集的namespace为SanXia.Resources,而且你在此项目下建立了文件夹,分了很多层,比如建立了Res文件夹,在Res下又建立了Text文件夹,Text下面你又建立了Html文件夹,Html下有demo.html文件,那么这个demo.html的resourceFilename就是:Res.Text.Html.demo.html。 资源程序集中资源的命名方式为:assemblyName+"."+resourceFilename 。因此我们需要得到资源程序集的名称和所要呈现在Web页面上的资源文件名称,于是需要给自定义的Handler传递这两个参数,这里设计的传递格式为resourceFilename-assemblyName,如果没有提供assemblyName,也就是说如果传递的格式为resourceFilename,那么我们就会在web.config文件里appSettings节查找key为ResourceAssemblyName的项,读取它所对应的value,我们就把它当作资源程序集的名称。

3。
    为了防止其它不相关的网站随意盗链本网站上的资源,因此在资源类内部对次进行了必要的处理,如果是来自非本站主机的链接,程序就是去读取一个默认资源程序集里的资源,提供给web页来呈现,也就是一个提示性的说明图片,比如说你出错了等等。这个默认的资源程序集的名称同样可以在web.config里的appSettings节配置,它的key为DefaultResourceAssemblyName。同时,关于本站资源主机的设置也是可以在配置文件中处理的,它所对应的Key为ResourceHostName。

4。
    在web上呈现的资源名称是大小写免感的,不能写错噢,如果不小心写错了,系统会自动加载默认资源程序集里的错误资源,提示加载资源出错了。

    我们把前面的理解一下,如果不明白可以多读几次,下面我们就要开始编写代码了。

 

一。SanXia.Components.Resource类

    最主要的我们需要编写一个资源类,让它负责处理所有各种不同的资源,比如文字资源,图像资源,音视频资源等。我们把这个资源类的命名空间设为SanXia.Components.Resource,生成后的DotNET Dll名称也设为SanXia.Components.Resource。

    我们定义两个接口,分别为IResourceProvider和IRenderResource,以及两个抽象基类,分别为ResourceProviderBase和RenderResourceBase

   
我们先介绍一下IResourceProvider接口,它很简单,简单到只有一个方法,返回另外的一个接口IRenderResource。
    为什么要这么设计?是因为要充分利用运行时多态特性,也就是即于接口编码,恩,这句话好像是很熟悉,是不是在那里看到过......“到处都是这么说滴~~“。有什么好处吗?接口和实现分开,这还不够好吗?当然不只是这个的原因,其实你也不这么设计,不过前人已经总结出了那么好的解决方案干嘛重新造轮子去呢。


    namespace SanXia.Components.Resource.Provider
    {
        /// <summary>
        /// Description : 资源提供者接口
        /// Author:CHONGCHONG-天真的好蓝
        /// QQ:1921069
        /// MSN:chongchong2008@msn.com
        /// Address : YanJiang Road 7# YiChang HuBei CHINA
        /// Date:2006-08-29
        /// LastModifyDate:2006-08-29
        /// </summary>
        public interface IResourceProvider
        {
            IRenderResource RenderResource(); //返回生成资源接口
        }
    }

    再说说IRenderResource接口,噢......它同样设计得相当简单,就一个方法Render,用它来呈现资源。


    namespace SanXia.Components.Resource
    {
        public interface IRenderResource
        {
            void Render();
        }
    }

    看看其它两个抽象类,首先来瞄瞄ResourceProviderBase abstract class,它是这样定义的:
    namespace SanXia.Components.Resource.Provider
    {
        public abstract class ResourceProviderBase
        {
            #region //字段定义
            protected string resourceType; //资源类型
            protected string resourceName; //资源名称
            protected string assemblyName; //程序集名称
            #endregion

            #region //构造方法
            public ResourceProviderBase(string resourceType, string resourceName, string assemblyName)
            {
                this.resourceType = resourceType;
                this.resourceName = resourceName;
                this.assemblyName = assemblyName;
            }
            #endregion
        }
    }
    看上去没啥作用,只是用作公共状态的存储而已嘛,这个倒是没什么好说的了。

    继续看看另为一个抽象类的定义,RenderResourceBase abstract class,它是这样定义的:
    namespace SanXia.Components.Resource
    {
        public abstract class RenderResourceBase
        {
            #region //字段定义
            private NameValueCollection appSettings;
            private string defaultResourceAssemblyName;
            private Cache assemblyCache;

            protected string resourceType; //资源类型
            protected string resourceName; //资源名称
            protected string assemblyName; //程序集名称
            protected Assembly resourceAssembly = null; //资源程序集
            protected HttpContext context; //当前上下文
            protected byte[] buffer; //资源数据
            protected Stream resourceStream; //资源流
            protected string contentType; //资源输出内容类型
            protected Encoding contentEncoding; //资源输出内容编码
            #endregion

            #region //构造方法
            public RenderResourceBase(string resourceType, string resourceName, string assemblyName)
            {
                appSettings = System.Web.Configuration.WebConfigurationManager.AppSettings;
                defaultResourceAssemblyName = appSettings["DefaultResourceAssemblyName"];
                string defaultResourceFilename = defaultResourceAssemblyName + ".Res.FileNotFound.gif";

                this.assemblyCache = new System.Web.Caching.Cache();
                this.resourceType = resourceType;
                this.resourceName = assemblyName + "." + resourceName;
                this.assemblyName = assemblyName;
                this.context = HttpContext.Current;
                this.resourceAssembly = Assembly.Load(this.assemblyName);//可以自己加入缓存机制

                if (ResourceHelp.IsResourceExists(this.resourceName, this.resourceAssembly))
                {
                    this.resourceStream = resourceAssembly.GetManifestResourceStream(this.resourceName);
                    int bytes = (int)resourceStream.Length;
                    buffer = new byte[bytes];
                    resourceStream.Read(buffer, 0, bytes);
                }
                else
                {
                    //指定的资源不存在,显示默认资源
                    this.resourceAssembly = Assembly.Load(defaultResourceAssemblyName);
                    this.resourceStream = resourceAssembly.GetManifestResourceStream(defaultResourceFilename);
                    int bytes = (int)resourceStream.Length;
                    buffer = new byte[bytes];
                    resourceStream.Read(buffer, 0, bytes);
                }
            }
            #endregion
        }
    }
    稍微有点繁琐,完成了大部分重要工作,就是从web.config读取默认资源程序集得名称,判断传递过来的resourceFilename是否在assembly里存在,这里用了ResourceHelp.IsResourceExists这个方法来判断。获取资源流赋给resourceStream保护变量,然后将它读入buffer字节数组保护变量,如果判断资源不存在或是来自非本站的盗链,就会加载默认资源程序集,


    重要的任务都做好了,下面再继续处理ResourceProvider,比如我们要加入一个ImageResourceProvider,我们只需要继承ResourceProviderBase抽象基类,同时实现IResourceProvider接口,DotNET不允许同时继承两个基类,那么利用接口就可以解决此限制。
    看看ImageResourceProvider类是如何设计的:
    namespace SanXia.Components.Resource.Provider
    {
        public sealed class ImageResourceProvider : ResourceProviderBase, IResourceProvider
        {
            #region //构造方法
            public ImageResourceProvider(string imageType, string resourceName, string assemblyName)
                : base(imageType, resourceName, assemblyName)
            { }
            #endregion

            #region //接口实现
            /// <summary>
            /// Description:返回生成资源接口
            /// </summary>
            /// <returns></returns>
            public IRenderResource RenderResource()
            {
                return new RenderImageResource(base.resourceType, base.resourceName, base.assemblyName);
            }
            #endregion
        }
    }

    首先构造函数它接受imageType,resourceName,assemblyName三个参数,然后把它传给基类的相关保护型变量。
    由于它继承IResourceProvider接口,所有要实现IResourceProvider接口的RenderResource方法,前面提到过这个接口,实现它即可以,
    因为是图像资源提供者类,所有它也是有选择的返回生成图像资源类,RenderImageResource实现了IRenderResource接口。


    那么RenderImageResource类是如何设计的呢,下面来看看代码:
namespace SanXia.Components.Resource
{
    public class RenderImageResource : RenderResourceBase, IRenderResource
    {
        #region //字段定义
        protected ImageFormat imageFormat; //图像格式
        #endregion

        #region //构造方法
        public RenderImageResource(string imageType, string resourceName, string assemblyName)
            : base(imageType, resourceName, assemblyName)
        { }
        #endregion

        #region //接口实现
        /// <summary>
        /// Description:实现接口方法,生成资源
        /// </summary>
        public void Render()
        {
            switch (base.resourceType)
            {
                case "gif":
                    base.contentType = "image/gif";
                    this.imageFormat = ImageFormat.Gif;
                    break;
                case "jpg":
                case "jpeg":
                    base.contentType = "image/jpeg";
                    this.imageFormat = ImageFormat.Jpeg;
                    break;
                case "png":
                    base.contentType = "image/png";
                    this.imageFormat = ImageFormat.Png;
                    break;
                default:
                    base.contentType = "application/octet-stream";
                    break;
            }
            context.Response.Clear();
            context.Response.ContentType = base.contentType;
            Image image = Image.FromStream(new System.IO.MemoryStream(base.buffer)); //其实流也是可以直接访问到的,因为resourceStream是保护型的变量。
            image.Save(context.Response.OutputStream, imageFormat);
        }
        #endregion
    }
}
    由于RenderImageResource继承了RenderResourceBase抽象基类,所以RenderImageResource不需要做很复杂的工作,它直接调用基类来帮自己处理。构造方法就只把相关参数传给了基类。这样看来,设计一个抽象基类还是很不错的,子类继承了基类的保护型变量,可以直接访问到,比如可以直接访问到stream类型的resourceStream。
它现在要做的只是实现IRenderResource接口就可以了,就只有一个Render方法,用来呈现具体的web资源。

    其它的资源,比如文字等都可以按照上面那样设计,需要一个provider和render。具体代码请下载全部源文件自己研读。
到此,可以放松一下了,需要的各种资源类都设计好了,现在把它们提供给外部调用,为此又加了一个资源工厂类。把各种资源提供给外部,它的定义如下:
namespace SanXia.Components.Resource
{
    public sealed class ResourcesFactory
    {
        #region //返回资源
        public static IRenderResource CreateResource(string resourceName, string assemblyName)
        {
            IResourceProvider resourceProvider = null; //资源提供者接口
            IRenderResource renderResource = null; //生成资源接口

            string[] imageType = "gif,jpg,jpeg,png".Split(",".ToCharArray());
            string[] textType = "vbs,js,htc,css,txt,xsl,xml,htm,html".Split(",".ToCharArray());
            string[] videoType = "mp3,wmv,wma,mpg,mpa,mpe,mov,qt,avi,asf,asr,asx,swf".Split(",".ToCharArray());
            string resourceExt = resourceName.Substring(resourceName.LastIndexOf(".") + 1).ToLower();

            if (!ResourceHelp.IsRobber())
            {
                if (ResourceHelp.IsTypeExist(resourceExt, imageType))
                {
                    resourceProvider = new ImageResourceProvider(resourceExt, resourceName, assemblyName);
                    renderResource = resourceProvider.RenderResource();
                }
                else if (ResourceHelp.IsTypeExist(resourceExt, textType))
                {
                    resourceProvider = new TextResourceProvider(resourceExt, resourceName, assemblyName);
                    renderResource = resourceProvider.RenderResource();
                }
                else if (ResourceHelp.IsTypeExist(resourceExt, videoType))
                {
                    resourceProvider = new VideoResourceProvider(resourceExt, resourceName, assemblyName);
                    renderResource = resourceProvider.RenderResource();
                }
            }
            else //如果是盗链
            {
                if (ResourceHelp.IsTypeExist(resourceExt, imageType))
                {
                    resourceProvider = new ImageResourceProvider(resourceExt, resourceName + "Robber", assemblyName);//这里人为添加Robber字符是为了显示默认错误图像,此资源在SanXia.Resources.DefaultResource.dll里,可以在Web.config配置
                    renderResource = resourceProvider.RenderResource();
                }
            }

            return renderResource; //返回资源接口,运行时多态
        }
        #endregion
    }
}


    就这么简单,资源类就算是完成了。

 

二。SanXia.Components.Handler类
    回忆一下最前面所说的,要在Web页面上呈现资源程序集里的资源,还需要提供一个自定义的Handler,只需要继承IHttpHandler接口即可。
这个类需要引用上面封装好的Resource类。

using SanXia.Components.Resource;//引入资源处理类
namespace SanXia.Components.Handler
{
    /*
     * 功能:
     *  提取指定程序集中嵌入的资源文件,并输出到客户端
     *   支持的资源类型如下
     *  gif,jpg
     *  vbs,js,css,txt,xml,htm,html
     *  flash,mpeg
     *  wav,mp3,avi,wma
     *
     * 调用:
     *  此资源类提供HttpHandler来提取指定程序集中的资源
     *  因此你需要在你的IIS数据库里映射扩展类型,本资源类默认使用.res扩展名
     *  <img src=".res?Res.Image.demo.jpg" />
     *          <img src=".res?Res.Image.demo.jpg-SanXia.Resources" />
     *
    */
    /// <summary>
    /// Description : 资源Handler类
    /// Author:CHONGCHONG-天真的好蓝
    /// QQ:1921069
    /// MSN:chongchong2008@msn.com
    /// Address : YanJiang Road 7# YiChang HuBei CHINA
    /// Date:2006-08-29
    /// LastModifyDate:2006-08-29
    /// </summary>
    public class ResourceHandler : IHttpHandler
    {
        #region //字段定义
        private NameValueCollection appSettings;
        #endregion

        #region //构造方法
        public ResourceHandler()
        {
            appSettings = System.Web.Configuration.WebConfigurationManager.AppSettings;
        }
        #endregion

        #region //接口实现
        void IHttpHandler.ProcessRequest(HttpContext context)
        {
            IRenderResource renderResource ; //生成资源接口
            string resourceName = String.Empty; //资源名称
            string assemblyName = String.Empty; //程序集名称
            string queryParams = String.Empty;
            string[] queryParams1,queryParams2;

            //传递过来的全部路径参数,包含cookie , forms 等
            string fullParams = context.Request.Params.ToString();

            //截取QueryString查询参数偏移量
            int offset = fullParams.IndexOf("&ALL_HTTP");

            //获取query参数
            queryParams = fullParams.Substring(0, offset);
            queryParams1 = HttpUtility.UrlDecode(queryParams).Split(new char[] { '&' });

            queryParams2 = queryParams1[0].Split(new char[] { '-' });

            if (queryParams2.Length == 1) //只传递了资源名
            {
                resourceName = queryParams2[0].ToString();
                assemblyName = appSettings["ResourceAssemblyName"];
            }
            else //同时传递了资源名和资源程序集名称
            {
                resourceName = queryParams2[0].ToString();
                assemblyName = queryParams2[1].ToString();
            }

            renderResource = ResourcesFactory.CreateResource(resourceName, assemblyName); //直接调用封装好的ResourcesFactory类的静态方法CreateResource
            renderResource.Render(); //向Web页面呈现当前资源
        }

        bool IHttpHandler.IsReusable
        {
            get { return true; }
        }

        #endregion
    }
}


三。Web Test

















    一切都准备好了,剩下的就要说说web.config配置文件了,在appSettings 节需要添加3个Key-Value对。如下:
    <appSettings>
     <!--
        ResourceHostName:资源所在主机,如同www.aaa.net可以防止其他网站盗链图像资源,大家在测试的时候要注意填写正确
        ResourceAssemblyName:资源程序集名称
        DefaultResourceAssemblyName:默认资源程序集名称,当资源加载失败时会从默认资源程序集提取资源
     -->
     <add key="ResourceHostName" value="localhost"/>
     <add key="ResourceAssemblyName" value="SanXia.Resources"/>
     <add key="DefaultResourceAssemblyName" value="SanXia.Resources.DefaultResource"/>
    </appSettings>

    然后配置自定义的Handler,同时你也需要的IIS里添加映射。我这里取的名字是.res,当然这个可以随意取。
    <system.web>
      <httpHandlers>
        <add verb="*" path="*.res" type="SanXia.Components.Handler.ResourceHandler,SanXia.Components.Handler.ResourceHandler"/>
      </httpHandlers>
      <globalization requestEncoding="utf-8" responseEncoding="utf-8" />
    </system.web>

    在Html页面显示资源,调用如下:
    <link href=".res?Res.Text.Css.demo.css" rel="stylesheet" type="text/css"/>
    <script type="text/javascript" src=".res?Res.Text.Script.demo.js"></script>
    <img src=".res?Res.Image.demo1.jpg" />
    <img src=".res?Res.Image.demo2.jpg-SanXia.Resources" />


 源代码下载

 http://files.cnblogs.com/CHONGCHONG2008/SanXia.Resource.Setup.rar

 

posted on 2007-03-27 20:04 天真的好蓝啊 阅读(2987) 评论(9)  编辑 收藏 所属分类: C#.NET

Feedback

#1楼  2007-03-27 21:17 Cat Chen      

这世界上根本不存在绝对有效的防盗链方法,所以这一切都是比较徒劳的……要么防范多了,要么防范少了,总之不存在精确的。   回复  引用  查看    

#2楼  2007-03-28 00:20 Artech      

@Cat Chen
粗劣地看了上面的内容,他的方法是把资源放在Resource里,并编译成Assembly。在Web Page中通过自定Path(path 对应Custom HttpHandler配置的Path)、Assembly Name,和Resource,由Custom HttpHandler通过Reflection机制获得Resource。

我想问一下如果我通过这样的Address:http://hostName/xxx.res?Res.Image.demo1.jpg会不会得到这个Image呢?

还有一个问题,通过Reflection对性能的影响程度有多大?   回复  引用  查看    

#3楼  2007-03-28 01:06 Cat Chen      

@Artech
我觉得只要得到相同的URL,在此Handler中就能得到相同的资源,所以我也不明白到底如何防盗链。   回复  引用  查看    

#4楼 [楼主] 2007-03-28 09:08 天好蓝-崇崇      

@Artech
自己 可以把代码 下载 在 本机 测试 一下 ~~~   回复  引用  查看    

#5楼 [楼主] 2007-03-28 09:09 天好蓝-崇崇      

@Cat Chen

那 你自己测试过没有呢 ~~
非本站连接的你看能不能读到你要的资源~   回复  引用  查看    

#6楼  2007-03-28 10:25 sharper.shen [未注册用户]

可以增加一层Cache,这样不需要一直进行Reflection,效率会高一些。   回复  引用    

#7楼  2007-03-28 11:23 张小峰 程序员的原创吉他谱天地 www.gtp.cn [未注册用户]

真是不错   回复  引用    

#8楼  2007-03-28 14:30 双鱼座      

@Cat Chen
其实所谓的防盗链就是判断HttpContext.Current.Request.UrlReferrer.Host是不是本机或者配置中的指定主机。这个应该可以通过自己写的HttpClient而不是浏览器实现web request欺骗来实现。所以,你说的“这世界上根本不存在绝对有效的防盗链方法”是非常正确的。
重要的是:这个和将内容保存到资源中有什么关系呢?本质上,他的文章其实讲的是一个Url Rewrite的问题。以前见过一篇文章讲,通过.axd来实现Url Rewrite(当然实现从资源获得Response内容是很简单的事情),这样却省了配置IIS的麻烦。   回复  引用  查看    

#9楼  2007-03-29 09:11 天很蓝 [未注册用户]

@sharper.shen

是的。我已经在类里面提供了System.Web.Caching.Cache
private Cache assemblyCache;

this.resourceAssembly = Assembly.Load(this.assemblyName);//可以自己加入缓存机制
在Assembly.Load的时候装到Cache里就可以了。

其实这个资源本身也可以输出stream对象,也就是说,可以直接在.cs代码里直接读取需要的资源,不过你需要自己在IRenderResource接口添加一个重载的方法。stream Render();


public interface IRenderResource
{

void Render();
stream Render();//stream在base抽象类可以访问到,是保护类型的
}
  回复  引用    



标题  
姓名  
主页
Email (只有博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2007-05-21 09:28 编辑过
 
另存  打印
 


My Links

Blog Stats

News

与我联系

常用链接

留言簿(1)

我管理的小组

我的标签

随笔分类

随笔档案

相册

搜索

最新评论

阅读排行榜

评论排行榜

集从人之所长,创造博客社区神话,实现梦想!