• 20 Nov 2008 /  ASP

    在写完了php的数据库类之后,发现其实ASP也是可以做到数据库连接对象隐藏的!现在最主流的ASP写法都是将conn做为全局变量。以前封装的几个类也或多或少的用到了conn对象。也就是在配置文件里必须有一个 conn的全局变量,我们需要的时候用 link_database() 来给conn赋值,然后在页面的结尾都需要显示的conn.close();通常我们在每个页面的结尾加上如下代码:

    if isobject(conn) then conn.close:set conn=nothing

    这样做存在什么问题呢?不优美了!相比自己写的PHP类,显得耦合度很高,我竟然需要用到全局变量来控制数据库操作!有没有更好的方法呢?以前也想过,不过最后因为过度封装反而影响了开发的速度,结果还是回到最原始的conn全局变量的写法来。其实要100%封装ASP的数据库操作是完全可以的!下面先来看下我们理想的写法。数据库更新删除添加我就不写了,没什么好说的,我们可以做到。要封装最麻烦的是和分页结合在一起的部分。如果我用调用PHP数据库操作类的写法应该如下:

    Set objConn=New DBClass ‘建立一个新的对象
    objConn.Connection() ‘连接数据库
    objConn.PageSize=20 ‘一页显示20条
    objConn.Execute(”select * from T”) ‘传入select语句,自动判断是查询还是更新

    ‘以下是循环读取数据
    while(objConn.Read())
              Response.write objConn.Get(”ID”) & “ ”
              Response.write objConn.Get(”test”) & “<br />”
    Wend
    objConn.ShowPage() ‘分页导航

    看上面的写法多么优美!我们没有任何的连接数据库的对象。我们希望不需要写conn.close(),只要页面销毁对象的时候自动关闭数据库连接,我们希望不需要考虑页面的参数,ShowPage() 已经帮我们完成了。我们也不希望去考虑数据如何传递,我们只需要简单的Get就可以获得我们想要的数据。想想,如果能这样,我可以用VB去封装一个数据库操作的类,我可以直接用 Set objConn=Server.Create(”DBClass”) 来生成一个对象,VB是编译好了的,性能肯定很高。这样的写法能做到吗?答案是肯定的!完全可以通过ASP有限的面向对象特性封装成一个这样的类。

    首先我们分析下,这个类我们需要将conn,rs等 传统的东西都包含进去。也就是我们在调用Connection方法的时候就得产生一个conn对象,当然这个对象是类里面私有的。然后我们在给PageSize赋值的时候,我们的对象就知道接下来是要进行分页操作了!当执行Execute的时候,我们同样也判断是否是select 语句,如果是,则产生一个rs对象,这个rs肯定也是私有的。那如何得到值呢?以前一直被rs(”id”)这种方式误导了,认为要构建一个数据库操作类,应该和C#那样objConn(”ID”)来得到,其实应该是用类里面的一个方法,暂且我们定义为Get(str) 我们可以将Get映射到 rs(str) 里面来,这样就可以在外部得到想要的一切数据了。好,还有另外的情况!rs.movenext呢?我们需要控制游标下移啊。在这里我用类似.net 里面的Read()方法来完成。其实很好做,反过来,如果是第一条记录,直接给返回true。如果不是第一条记录,怎么办呢?控制rs.movenext()嘛。这样我们的写法不就和.net里面统一了吗? 所以通过Get完全可以获得我们想获得的任何记录。至于分页,能顺利进行吗?答案是肯定的!其实一个分页函数只和2个变量有关!第一个是最大值,第2个是一页显示多少条记录。而至于当前页,完全可以通过全局的request对象获得参数值。第1个最大值,可以调用rs的recodcount得到,一页显示多少条,前面的变量已经赋了。所以分页也可以解决!

    通过以上的分析一个优美的数据库操作类就完成了。这个类我们完全可以独立运行,不需要任何所谓的全局conn对象。而且非常健壮,在类撤销的时候,会自动关闭数据库连接。

    到了上面的分析,基本上解决了问题,但是有一个问题:海量数据的分页!肯定不能用rs来分页,如果超过10万条,都放到内存,还不慢死!可惜sqlserver和access 都不支持limit,支持该多少好啊。当然不支持不意味着不能实现高性能的分页。获得记录条数,还是改造select语句,将其改造为 select count(*)。而取得数据记录用 排除法,select top not in 这种方法来实现。当然这里的改造select 语句可比mysql的要复杂得多啊,毕竟是将一条select 语句改造成一条复合语句。以前封装C#的数据库操作类的时候已经封装了一个,相信看看以前的代码处理起来会比较简单。当然类里面肯定得做开关来支持海量数据分页。

    至此,这个数据库操作分页类就分析完毕了,至于代码,暂不公布,还没写完,这种方式还是昨天坐车的时候想到的。写了这么久的程序,接触了VB,VC++,C#,Java,ASP,到前段时间接触的PHP,可谓各大主流的编程语言都过了一遍了。越来越深刻的感觉到,任何一种语言其实都可以写得很优美!任何一种语言都可以提高可复用性,提高健壮性。

    其实我还感觉ASP还可以实现更多的面向性。因为ASP有个很有用的函数:execute .将字符串当表达式执行!以前一直是用来表单给rs赋值用的。也许在这个数据库操作类里面我们可以加入更新,添加的部分。用execute语句可以像这样来更新数据:objConn.Form2Rs(”ID,UserName,Password,TX”) 只要这样一写,表单的值就全跑到数据库去了。当然必须Form名和数据库字段名对应!

    其实有时候想来,编程和设计其实并没有区别,让代码看起来更优美,更简单是不断追求的。

    Tags: ,

  • 12 Nov 2008 /  ASP

    最近一个朋友的动网论坛老被人攻击,CPU利用率一直居高不下,只要把论坛目录一改名,CPU利用率马上下来!找了半天都无法找到原因,初步估计是有人写了程序恶意访问他的服务器,造成某个ASP页面一直打开关闭数据库,使得CPU居高不下。在任务管理器查看服务器线程数,关闭他的IIS进程池,线程数 760个,打开进程池,线程数 810个,也就是说有人伪造了50个访问不停的刷他的某个ASP页面。如何解决?

    首先分析他的代码,发现网页全部生成静态文件,不需要处理,而论坛全部都调用conn.asp文件。好像可以解决了,于是我写了一个check.asp,conn.asp包含这个文件。

    然后在check.asp 里面,加入判断代码,思路是:如果是用程序提交访问,势必没有cookie,如果能提交cookie也无法获得cookie!那么每次访问,我就先判断是否有cookie,没有就写一个cookie,再判断cookie是否为我设置的那个数,如果是,则访问,不是马上Response.End() 断掉请求。开始测试,效果挺理想的,可是后来还是CPU利用率很高!怎么办呢? 开始设想,可定是他为找了cookie!mygod,竟然伪造cookie,怎么办呢?接着想办法,如果我一次给他分配2个Cookie,一个随机,另一个是这个cookie通过一个算法演变的,然后服务器端判断2个cookie通过这个算法是否相等!好像问题终于可以解决了。其实还差一点,如果是搜索引擎呢?搜索引擎肯定无法抓取这样的页面,那么在写cookie之前,先判断是否为蜘蛛,至此,问题基本解决!

    代码如下:

    <%
    ‘检查是否是蜘蛛人
    function check(user_agent)
     allow_agent=split(”Baiduspider,Scooter,ia_archiver,Googlebot,FAST-WebCrawler,MSNBOT,Slurp”,”,”)
     check_agent=false
     for agenti=lbound(allow_agent) to ubound(allow_agent)
      if instr(user_agent,allow_agent(agenti))>0 then
       check_agent=true
       exit for
      end if
     next
     check=check_agent
    end function

    Sub LT_Check()
     dim user_agent,http_reffer,server_name,chk_rnd 
     

     user_agent=Request.ServerVariables(”HTTP_USER_AGENT”)
     http_reffer=Request.ServerVariables(”HTTP_REFERER”)
     server_name=Request.ServerVariables(”SERVER_NAME”)
     if not check(user_agent) then
      if Request.Cookies(”check_1a”)=”" then
       randomize
       chk_rnd=rnd
       Response.Cookies(”check_1a”)=chk_rnd
       Response.Cookies(”check_22″)=Request.Cookies(”check_1a”)*0.3435  end if
      ’response.write Request.Cookies(”check_22″)
      if cstr(Request.Cookies(”check_22″))<>cstr(Request.Cookies(”check_1a”)*0.3435) then response.end()
     end if
    End SUb

    call LT_Check()
    %>

    在这里我用了最简单的一个算法,直接让第2个cookie乘以 一个数,这样就出了2个cookie了,然后验证这2个cookie是否相等,如果攻击者想伪造Cookie似乎很困难。当然,如果我是攻击者,看到这段代码,我马上就能写出相应的攻击的程序,只要伪造2个数,一个数是另一个的0.3435就可以了嘛!但是,攻击者在不看这个文章的时候,能一下想到这个方法还是很难的,毕竟随机数嘛,而且如果他花几个小时想到了破解方法,我马上改个算法,把数字变成字符,我想他肯定很郁闷,几个小时的成果白费,又得破解我的算法。呵呵~~~~

    引申:

    从这里可以看出,其实我们的目的就是要让攻击者费点功夫让他自难而退。如果太复杂,太花精力,谁来做啊。当然,还有一个办法,就是伪造成蜘蛛来爬!我这里做了一个测试,其实一般的蜘蛛完全可以不防,因为我的采集程序照样可以访问里面的内容!后来我将cookie打印出来,发现采集程序虽然可以访问内容,但是每次cookie都不相同!而用浏览器访问,每次cookie都相同!何解? 这证明,马上设置cookie,马上获得其实还是在服务器的内容里面!从这个角度来说,是不是无法防止攻击呢?因为别人可以不停的采集这个页面啊!这更本没防住嘛。可是事实证明,2天了,没有出现CPU太高的情况!证明已无攻击或者防住了攻击了!怎么解释呢?

    这里有2种可能:1)正常采集,也就是调用windows的XMLHTTP组件的采集,是可以识别cookie的!虽然不能保存cookie,但是asp的cookie对他同样生效!但这种方式的访问,和正常访问无区别,都属于正常访问的范畴,不会对服务器造成影响!而攻击者用的访问,显然应该属于CC攻击一类的,这种攻击请求,以消耗CPU利用率为目的,不能识别cookie,更不能保存cookie,所以一旦用cookie验证,这种攻击基本失效,因为每次都会response.end()掉,更本不读数据库,性能无损失!如果这样的话,前面蜘蛛识别其实不用了,因为蜘蛛也是用类似xmlhttp的方式访问的,能很好的采集!

    2)我一加上程序,攻击者没发动攻击了,所以一切正常了!(好像不太可能啊,每天都他会攻击几次!)

    此文抛砖引玉,欢迎路过的朋友给点自己的看法。ASP是我一直使用的一种编程技术,虽然语言太不优美,但是结合VB,VC++,很多场合应用都很过得去。

    Tags: ,