分页: 8/25 第一页 上页 3 4 5 6 7 8 9 10 11 12 下页 最后页 [ 显示模式: 摘要 | 列表 ]
Jun 30
在入门篇,我们学会了SQL注入的判断方法,但真正要拿到网站的保密内容,是远远不够的。接下来,我们就继续学习如何从数据库中获取想要获得的内容,首先,我们先看看SQL注入的一般步骤:

  第一节、SQL注入的一般步骤

  首先,判断环境,寻找注入点,判断数据库类型,这在入门篇已经讲过了。

  其次,根据注入参数类型,在脑海中重构SQL语句的原貌,按参数类型主要分为下面三种:

  (A) ID=49 这类注入的参数是数字型,SQL语句原貌大致如下:
  Select * from 表名 where 字段=49
  注入的参数为ID=49 And [查询条件],即是生成语句:
  Select * from 表名 where 字段=49 And [查询条件]


  (B) Class=连续剧 这类注入的参数是字符型,SQL语句原貌大致概如下:
  Select * from 表名 where 字段=’连续剧’
  注入的参数为Class=连续剧’ and [查询条件] and ‘’=’ ,即是生成语句:
  Select * from 表名 where 字段=’连续剧’ and [查询条件] and ‘’=’’

  (C) 搜索时没过滤参数的,如keyword=关键字,SQL语句原貌大致如下:
  Select * from 表名 where 字段like ’%关键字%’
  注入的参数为keyword=’ and [查询条件] and ‘%25’=’, 即是生成语句:
  Select * from 表名 where字段like ’%’ and [查询条件] and ‘%’=’%’

  接着,将查询条件替换成SQL语句,猜解表名,例如:

  ID=49 And (Select Count(*) from Admin)>=0

  如果页面就与ID=49的相同,说明附加条件成立,即表Admin存在,反之,即不存在(请牢记这种方法)。如此循环,直至猜到表名为止。

  表名猜出来后,将Count(*)替换成Count(字段名),用同样的原理猜解字段名。

  有人会说:这里有一些偶然的成分,如果表名起得很复杂没规律的,那根本就没得玩下去了。说得很对,这世界根本就不存在100%成功的黑客技术,苍蝇不叮无缝的蛋,无论多技术多高深的黑客,都是因为别人的程序写得不严密或使用者保密意识不够,才有得下手。

  有点跑题了,话说回来,对于SQLServer的库,还是有办法让程序告诉我们表名及字段名的,我们在高级篇中会做介绍。


  最后,在表名和列名猜解成功后,再使用SQL语句,得出字段的值,下面介绍一种最常用的方法-Ascii逐字解码法,虽然这种方法速度很慢,但肯定是可行的方法。

  我们举个例子,已知表Admin中存在username字段,首先,我们取第一条记录,测试长度:

  http://www.19cn.com/showdetail.asp?id=49 ;;and (select top 1 len(username) from Admin)>0

  先说明原理:如果top 1的username长度大于0,则条件成立;接着就是>1、>2、>3这样测试下去,一直到条件不成立为止,比如>7成立,>8不成立,就是len(username)=8

  当然没人会笨得从0,1,2,3一个个测试,怎么样才比较快就看各自发挥了。在得到username的长度后,用mid(username,N,1)截取第N位字符,再asc(mid(username,N,1))得到ASCII码,比如:

  id=49 and (select top 1 asc(mid(username,1,1)) from Admin)>0

  同样也是用逐步缩小范围的方法得到第1位字符的ASCII码,注意的是英文和数字的ASCII码在1-128之间,可以用折半法加速猜解,如果写成程序测试,效率会有极大的提高。

  第二节、SQL注入常用函数

  有SQL语言基础的人,在SQL注入的时候成功率比不熟悉的人高很多。我们有必要提高一下自己的SQL水平,特别是一些常用的函数及命令。

  Access:asc(字符) SQLServer:unicode(字符)

  作用:返回某字符的ASCII码

  Access:chr(数字) SQLServer:nchar(数字)

  作用:与asc相反,根据ASCII码返回字符

  Access:mid(字符串,N,L) SQLServer:substring(字符串,N,L)

  作用:返回字符串从N个字符起长度为L的子字符串,即N到N+L之间的字符串

  Access:abc(数字) SQLServer:abc (数字)

  作用:返回数字的绝对值(在猜解汉字的时候会用到)

  Access:A between B And C SQLServer:A between B And C

  作用:判断A是否界于B与C之间

  第三节、中文处理方法

  在注入中碰到中文字符是常有的事,有些人一碰到中文字符就想打退堂鼓了。其实只要对中文的编码有所了解,“中文恐惧症”很快可以克服。

  先说一点常识:

  Access中,中文的ASCII码可能会出现负数,取出该负数后用abs()取绝对值,汉字字符不变。

  SQLServer中,中文的ASCII为正数,但由于是UNICODE的双位编码,不能用函数ascii()取得ASCII码,必须用函数unicode ()返回unicode值,再用nchar函数取得对应的中文字符。

  了解了上面的两点后,是不是觉得中文猜解其实也跟英文差不多呢?除了使用的函数要注意、猜解范围大一点外,方法是没什么两样的。
Jun 30
随着B/S模式应用开发的发展,使用这种模式编写应用程序的程序员也越来越多。但是由于这个行业的入门门槛不高,程序员的水平及经验也参差不齐,相当大一部分程序员在编写代码的时候,没有对用户输入数据的合法性进行判断,使应用程序存在安全隐患。用户可以提交一段数据库查询代码,根据程序返回的结果,获得某些他想得知的数据,这就是所谓的SQL Injection,即SQL注入。

  SQL注入是从正常的WWW端口访问,而且表面看起来跟一般的Web页面访问没什么区别,所以目前市面的防火墙都不会对SQL注入发出警报,如果管理员没查看IIS日志的习惯,可能被入侵很长时间都不会发觉。

  但是,SQL注入的手法相当灵活,在注入的时候会碰到很多意外的情况。能不能根据具体情况进行分析,构造巧妙的SQL语句,从而成功获取想要的数据,是高手与“菜鸟”的根本区别。

  根据国情,国内的网站用ASP+Access或SQLServer的占70%以上,PHP+MySQ占L20%,其他的不足10%。在本文,我们从分入门、进阶至高级讲解一下ASP注入的方法及技巧,PHP注入的文章由NB联盟的另一位朋友zwell撰写,希望对安全工作者和程序员都有用处。了解ASP注入的朋友也请不要跳过入门篇,因为部分人对注入的基本判断方法还存在误区。大家准备好了吗?Lets Go...

  入 门 篇

  如果你以前没试过SQL注入的话,那么第一步先把IE菜单=>工具=>Internet选项=>高级=>显示友好 HTTP 错误信息前面的勾去掉。否则,不论服务器返回什么错误,IE都只显示为HTTP 500服务器错误,不能获得更多的提示信息。

  第一节、SQL注入原理

  以下我们从一个网站www.19cn.com开始(注:本文发表前已征得该站站长同意,大部分都是真实数据)。

  在网站首页上,有名为“IE不能打开新窗口的多种解决方法”的链接,地址为http://www.19cn.com/showdetail.asp?id=49,我们在这个地址后面加上单引号’,服务器会返回下面的错误提示:

  Microsoft JET Database Engine 错误 80040e14

  字符串的语法错误 在查询表达式 ID=49 中。

  /showdetail.asp,行8

  从这个错误提示我们能看出下面几点:

  1. 网站使用的是Access数据库,通过JET引擎连接数据库,而不是通过ODBC。

  2. 程序没有判断客户端提交的数据是否符合程序要求。

  3. 该SQL语句所查询的表中有一名为ID的字段。


  从上面的例子我们可以知道,SQL注入的原理,就是从客户端提交特殊的代码,从而收集程序及服务器的信息,从而获取你想到得到的资料。
  第二节、判断能否进行SQL注入

  看完第一节,有一些人会觉得:我也是经常这样测试能否注入的,这不是很简单吗?

  其实,这并不是最好的方法,为什么呢?

  首先,不一定每台服务器的IIS都返回具体错误提示给客户端,如果程序中加了cint(参数)之类语句的话,SQL注入是不会成功的,但服务器同样会报错,具体提示信息为处理URL时服务器上出错。请和系统管理员联络。

  其次,部分对SQL注入有一点了解的程序员,认为只要把单引号过滤掉就安全了,这种情况不为少数,如果你用单引号测试,是测不到注入点的

  那么,什么样的测试方法才是比较准确呢?答案如下:

  ①http://www.19cn.com/showdetail.asp?id=49

  ②http://www.19cn.com/showdetail.asp?id=49 ;;and 1=1

  ③http://www.19cn.com/showdetail.asp?id=49 ;;and 1=2

  这就是经典的1=1、1=2测试法了,怎么判断呢?看看上面三个网址返回的结果就知道了:

  可以注入的表现:

  ① 正常显示(这是必然的,不然就是程序有错误了)

  ② 正常显示,内容基本与①相同

  ③ 提示BOF或EOF(程序没做任何判断时)、或提示找不到记录(判断了rs.eof时)、或显示内容为空(程序加了on error resume next)

  不可以注入就比较容易判断了,①同样正常显示,②和③一般都会有程序定义的错误提示,或提示类型转换时出错。

  当然,这只是传入参数是数字型的时候用的判断方法,实际应用的时候会有字符型和搜索型参数,我将在中级篇的“SQL注入一般步骤”再做分析。

  第三节、判断数据库类型及注入方法

  不同的数据库的函数、注入方法都是有差异的,所以在注入之前,我们还要判断一下数据库的类型。一般ASP最常搭配的数据库是Access和SQLServer,网上超过99%的网站都是其中之一。

  怎么让程序告诉你它使用的什么数据库呢?来看看:

  SQLServer有一些系统变量,如果服务器IIS提示没关闭,并且SQLServer返回错误提示的话,那可以直接从出错信息获取,方法如下:

  http://www.19cn.com/showdetail.asp?id=49 ;;and user>0

  这句语句很简单,但却包含了SQLServer特有注入方法的精髓,我自己也是在一次无意的测试中发现这种效率极高的猜解方法。让我看来看看它的含义:首先,前面的语句是正常的,重点在and user>0,我们知道,user是SQLServer的一个内置变量,它的值是当前连接的用户名,类型为nvarchar。拿一个nvarchar的值跟int的数0比较,系统会先试图将nvarchar的值转成int型,当然,转的过程中肯定会出错,SQLServer的出错提示是:将nvarchar值 ”abc” 转换数据类型为 int 的列时发生语法错误,呵呵,abc正是变量user的值,这样,不废吹灰之力就拿到了数据库的用户名。在以后的篇幅里,大家会看到很多用这种方法的语句。

  顺便说几句,众所周知,SQLServer的用户sa是个等同Adminstrators权限的角色,拿到了sa权限,几乎肯定可以拿到主机的Administrator了。上面的方法可以很方便的测试出是否是用sa登录,要注意的是:如果是sa登录,提示是将”dbo”转换成int的列发生错误,而不是”sa”。

  如果服务器IIS不允许返回错误提示,那怎么判断数据库类型呢?我们可以从Access和SQLServer和区别入手,Access和SQLServer都有自己的系统表,比如存放数据库中所有对象的表,Access是在系统表[msysobjects]中,但在Web环境下读该表会提示“没有权限”,SQLServer是在表[sysobjects]中,在Web环境下可正常读取。

  在确认可以注入的情况下,使用下面的语句:

  http://www.19cn.com/showdetail.asp?id=49 ;;and (select count(*) from sysobjects)>0

  http://www.19cn.com/showdetail.asp?id=49 ;;and (select count(*) from msysobjects)>0

  如果数据库是SQLServer,那么第一个网址的页面与原页http://www.19cn.com/showdetail.asp?id=49是大致相同的;而第二个网址,由于找不到表msysobjects,会提示出错,就算程序有容错处理,页面也与原页面完全不同。

  如果数据库用的是Access,那么情况就有所不同,第一个网址的页面与原页面完全不同;第二个网址,则视乎数据库设置是否允许读该系统表,一般来说是不允许的,所以与原网址也是完全不同。大多数情况下,用第一个网址就可以得知系统所用的数据库类型,第二个网址只作为开启IIS错误提示时的验证。
Jun 30
如果你希望用ASP发送E-MAIL,你需要安装一个A S P部件。有几种第三方厂商的部件你可以使用。但是在IIS4下,你可以使用CDONTS。

  虽然名字奇怪,它是很容易使用的并且性能良好。如果你希望使用它,请跟随下面步骤。

  1.检查你是否安装了SMTP服务。OPTION PACK缺省安装时是包括SMTP服务的。

  SMTP服务安装后,在你的system32目录下会有一个文件叫CDONTS.DLL。

  2.你可以用下面的简单脚本通过A S P发送E-MAIL:

  

  $#@60;%

  Dim MailObject

  Set MailObject = Server.CreateObject("CDONTS.NewMail")

  MailObject.Send "[email protected]","[email protected]","My subject","My text"

  %$#@62;

  是不是很简单?

  

  发送附件

  CDONTS的一个常用特性是用来在E-MAIL中发送附件。代码也不难写。

  $#@60;%

  Dim MailObject

  Set MailObject = Server.CreateObject("CDONTS.NewMail")

  att_file="c:\attachments\StandardPolicy.txt"

  f_name="Policy.txt"

  MailObject.From="[email protected]"

  MailObject.To="[email protected]"

  MailObject.Subject="Subject Text Here"

  MailObject.Body="Body Text Here"

  MailObject.AttachFile att_file,f_name

  MailObject.Send

  %$#@62;

  第三方厂商部件

如果你对CDONTS不满意,下面的地址清单是你可以找到的第三方厂商部件(大多数你需要花钱购买)

  Blat - http://gepasi.dbs.aber.ac.uk/softw/Blat.html

  Try Looking through: http://www.15seconds.com/
Jun 25
技巧12:将常用数据复制到脚本变量中

  当访问ASP中的COM对象时,应该将常用对象数据复制到脚本变量中。着将减少COM方法调用。而COM方法调用代价相对比访问脚本数据更高。当访问Collection和
Dictonary对象时,这项技术也能消减高昂的查询代价。  


  通常,当准备不止一次访问一个对象数据时,应该将这个数据放当一个脚本对象中。

  这项优化的主要目标是Request变量(Form和QueryString变量)。例如,你的站点传递一个叫UserID的QueryString变量,假定在一个特定页UserID被引用十次。在ASP页面的顶部,将UserID的值赋给一个变量,来替代十次的调用Request
("UserID"),将接生9次COM调用。

  在实际中,访问COM属性或方法的昂贵代价可能比较隐蔽。下面是一个例子,显示一段普通的代码:

Foo.bar.blah.baz = Foo.bar.blah.qaz(1)
If Foo.bar.blah.zaq = Foo.bar.blah.abc Then ' ...

  下面是这段代码运行的步骤:

1. 变量Foo被解析为一个全局对象
2. 变量bar被解析为Foo的一个成员。这触发一次COM方法调用
3. 变量blash被解析为Foo.bar的一个成员。同样,这也触发一次COM方法调用
4. 变量qaz被解析为Foo.bar.blash的一个成员。对,这也触发一次COM方法调用
5. 调用 Foo.bar.blah.qaz(1)。一个或多个COM方法调用。获取图片?
6. 重复步骤1到步骤3来解析baz。系统不知道调用qaz是否会改变对象模型,所以步骤1到步骤3又执行了一次,来解析baz
7. 解析出baz是Foo.bar.blah的一个成员,执行属性put.
8. 重复步骤1到步骤3来解析zaq
9. 重复步骤1到步骤3来解析abc

  正如你所看到的,这是多么低效(并且慢)。快速的方法是按如下代码写VBScript:

Set myobj = Foo.bar.blah ' do the resolution of blah ONCE
Myobj.baz = myobj.qaz(1)
If Myobj.zaq = Myobj.abc Then '...

  如果你用的是VBScript 5.0或更后的版本,可以使用With语句:

With Foo.bar.blah
  .baz = .qaz(1)
  If .zaq = .abc Then '...
  ...
End With

  技巧13:避免使用可变数组

  尽量避免使用可变数组。既然关心性能,最好还是在数组初始化的时候就设置好它可能的最大大小。当然,这不是说你明知不需要几M的内存,但还是应该给数组分配了那么多。  


  下面的代码是一个无理使用Redim的示范:

<%
Dim MyArray()
Redim MyArray(2)
MyArray(0) = "hello"
MyArray(1) = "good-bye"
MyArray(2) = "farewell"
...
Redim Preserve MyArray(5)
MyArray(3) = "more stuff"
MyArray(4) = "even more stuff"
MyArray(5) = "yet more stuff"
%>

  简单地将数组初始化的时候定义成正确的大小(这里应该是5)远比Redim数组使它变大好。你可能会浪费一些内存(如果你最后没有用完所有的元素),但得到的是速度!

  技巧14:使用响应缓冲(Response Buffering)

  打开“响应缓冲”就可以缓冲整个页面的输出,这样可以减少向浏览器写的次数,提高了总体性能。每次写浏览器都要耗费一定的时间和资源,因此减少写浏览器次数能提高性能;同时,TCP/IP协议发送少的大块数据比发送多的小块数据效率更高。  


  有两种方法可以打开响应缓冲。首先,可以使用Internet Service Manager来打开整个应用的响应缓冲。这是推荐的方法。在IIS 4.0和IIS 5.0中,新建一个应用时,响应缓冲缺省是打开的。第二种方法:针对每个独立的ASP页面,可以通过在页面顶部放置如下代码来打开响应缓冲:

<% Response.Buffer = True %>

  这行语句必须在所有缓冲数据写之前执行(就是说,在所有HTML和通过Response.Cookies设置Cookie之前)。通常,最好是为整个应用打开响应缓冲;这样你就不用在每个ASP页面顶部写上面那条语句了。

Response.Flush

  对于响应缓冲,因为用户在看到东西之前必须等待整个页面生成,所以用户可能够感觉到ASP页面响应比较慢(虽然整体响应时间缩短了);对一个运行时间较长的页面,可以同过Response.Buffer = False 来关掉响应缓冲;但更好的策略是使用Reponse.Flush方法。这个方法把所有已经由ASP生成的HTML输出到浏览器中。例如,一个1,000行的大表,在写完100行之后,ASP可以调用Response.Flush来强制把结果写到浏览器中,这样,用户就可以在其余行生成之前先看到100行数据。这个技术能让你两全其美—响应缓冲和渐进式地在浏览器表现数据。

  (注意,在上面的1,000行表的例子中,很多浏览器在遇到</table>标记之前可能并不画出整个表。如果想让浏览器逐步显示出数据,可以将一个大表分成多个小表,然后对每个小表调用Response.Flush。新版本的IE会在下载完整个表之前显示表,并且如果指定了表的列宽,显示的速度会更快。)

  另外,当产生一个非常大的页面时,响应缓冲可能会消耗掉许多的服务器内存。这个问题也可以通过使用Response.Flush来解决。

  技巧15:脚本大块化和Response.Write语句

  VBScript语法<% = 表达式 %>把“表达式”的值写到ASP输出流中;但如果响应缓冲没有打开,每个这样的语句都会想浏览器写数据,就把网络流分成很多小的包。这样会慢。同样,零星的小段脚本和HTML导致频繁的在脚本引擎和HTML之间切换,降低了性能。因此,应该使用以下技巧:把小块内嵌表达式改成调用Response.Write。例如,在下面的例子中,每行的每个字段都向响应流中写数据,并且每行都在VBScript和HTML中切换:  


<table>
<% For Each fld in rs.Fields %>
  <th><% = fld.Name %></th>
<%
Next
While Not rs.EOF
%>
 <tr>
 <% For Each fld in rs.Fields %>
  <td><% = fld.value %></td>
 <% Next
 </tr>
 <% rs.MoveNext
Wend %>
</table>

  下面是更有效的代码,每行只向响应流中写一次。所有的代码包含在一个VBScript块中:

<table>
<%
 For each fld in rs.Fields
   Response.Write ("<th>" &amp; fld.Name &amp; "</th>" &amp; vbCrLf)
 Next
 While Not rs.EOF
  Response.Write ("<tr>")
  For Each fld in rs.Fields %>
   Response.Write("<td>" & fld.value & "</td>" & vbCrLf)
  Next
  Response.Write "</tr>"
 Wend
%>
</table>

  当响应缓冲被禁止时,这个技巧非成的有效。最好打开响应缓冲,然后再看看批量地Response.Write对性能的提高。

  技巧16:在进入长时运算之前使用Resonse.IsClientConnected

  如果用户感到不耐烦,他们可能在ASP页面计算他们的请求之前离开这个页面。如果他们点击刷新或是跳到服务器上的另一个页面,新的请求将位于ASP请求队列尾部,而中断的请求却在请求队列的中部;通常服务器在高负载情况下可能发生这种情况(服务器有很长的请求队列,同时请求次数也很多);而这种情况又使服务器的负载情况变得更加恶劣。如果用户已经断掉连接,没有必要再执行这个ASP页面(尤其当这是一个很慢、很耗资源的页面时);Response.IsClientConnected属性能检查出这种情况;如果属性返回False,就应该调用Resonse.End来结束剩余的页面。事实上,IIS 5.0使这种检查规律划--无论什么时候ASP准备执行一个新的请求,他先检查请求队列有多长;如果队列已经超过3秒钟,ASP就会检查客户端是否连接;如果客户端已经断开,ASP立即终止这个请求。可以使用AspQueueConnectionTestTime设置来调整3秒的超时。  


  如果有一个非常耗时的页面要执行,也可以在页面中检查
  Response.IsClientConnected。当响应缓冲打开时,在页面运行中使用
  Response.Flush也能给用户操作正在执行的感觉。

  注意:在IIS 4.0上,除非你先执行了Response.Write,否则Response.IsClientConnected的结果有可能不正确;如果响应缓冲已经打开,还必须先执行Response.Flush。在IIS 5.0上,就没有这个必要了, Response.IsClientConnected工作得很正常。无论任何情况,Response.IsClientConnected总要消耗一些时间,因此,只应该在执行耗时至少超过500ms的页面中执行。首要原则是,不要在一个紧密的循环中反复调用这个属性。

  技巧17:用<OBJECT>标记来实例化对象

  如果你想在Global.asa中引用一个不在所有的代码路径中使用的对象(特定的服务器 - 或应用 - 范围对象),使用<object runat=server id=objname>标记定义比用Server.CreateObject方法定义更为合适一点。因为Server.CreateObject立即创建对象,但如果过你以后不使用这个对象,则浪费了资源。<object id=objname>只是声明objname,但objname并没有真正创建;objname在第一次使用时才创建。

  技巧18:为ADO和其他组件使用类型库声明

  当使用ADO时,开发者往往通过包含adovbs.txt来访问ADO常量。这个文件必须包含在每个使用常量的页面里;而常量文件还想相当的大,大大增加了处理每页耗费的时间和资源。  



  IIS 5.0引入了绑定组件类型库的能力;允许只引用类型库一次,然后就可以在每个ASP页面中使用。每个页面不用在为编译常量文件而消耗资源;组件开发者也不用为ASP准备VBScript包含文件了。

  可以在Global.asa中放入如下语句,来访问ADO类型库:

<!-- METADATA NAME="Microsoft ActiveX Data Objects 2.5 Library"
    TYPE="TypeLib" UUID="{00000205-0000-0010-8000- 00AA006D2EA4}" -->


<!-- METADATA TYPE="TypeLib"
    FILE="C:&amp;#92;Program Files&amp;#92;Common Files&amp;#92;system&amp;#92;ado&amp;#92;msado15.dll" -->

  技巧19:在循环中避免进行字符串连接

  很多人喜欢用如下的循环生成字符串:  


s = "<table>" & vbCrLf
For Each fld in rs.Fields
  s = s & " <th>" & fld.Name & "</th> "
Next

While Not rs.EOF
  s = s & vbCrLf & " <tr>"
  For Each fld in rs.Fields
    s = s & " <td>" & fld.value & "</td> "
  Next
  s = s & " </tr>"
  rs.MoveNext
Wend

s = s & vbCrLf & "</table>" & vbCrLf
Response.Write s

  这种方法有一些问题。第一个就是在循环中连接字符串会使时间成二次方(quadratic)成长;或者说,运行这个循环的时间同记录的字段数目平方成正比。

  下面简单的例子能更清楚地看见本质:

s = ""
For i = Asc("A") to Asc("Z")
  s = s & Chr(i)
Next

  在第一次循环中,s等于"A";在第二次循环中,VBScript必须重新分配s的空间,并把字符串"AB"赋给s;在第三次循环中,又重新分配s的空间,重新赋值。在第N(26)次循环中,VBScript重新分配并复制了N次字符串给s,所以,总共是1+2+3+...+N=N*(N+1)/2次复制。

  在上面例子中,如果有100条记录,每个记录有5个字段,则内循环执行100*5=500次,所有复制和重新分配空间的次数相应的就是 500*500=250,000次;这还只是对一个很小的记录集。

  在这中情况下,可以通过用Response.Write或是内嵌脚本(<% = fld.value %>)来替换字符串连接来提高性能。如果响应缓冲已经(也应该被)打开,Response.Write只是向响应缓冲尾部添加数据,没有重新分配内存,因此非常高效。

  在一些特定的将ADO记录集转换为HTML表的情况中,可以考虑使用GetRows或GetString函数。

  如果使用JScript连接字符串,强烈推荐使用 += 操作符;就是说,用 s += "some string",不要使用 s = s + "some string"。

  技巧20:使用Server.Transfer代替Response.Redirect

  Response.Redirect告诉浏览器请求另外一页。这个函数经常用来把用户跳转到登录页面或错误页面。既然redirect强制产生新的页面请求,结果就是浏览器和Web服务器间做了两次交互,Web服务器不得不多处理一次额外的请求。IIS 5.0引入了一个新的函数:Server.Transfer;这个函数直接把运行权交给同一个服务器上的另一个页面;避免了额外的浏览器到Web服务器的交互,提高了性能。

  技巧21:在目录URL的尾部加上斜杠(/)

  如果省略了尾部的斜杠,浏览器回发送一个请求给服务器,被告知它的请求是一个目录;然后浏览器再发送一个二次请求,不过这次URL尾部加上了斜杠,然后服务器再次响应浏览器。如果一开始就给URL加上斜杠,便可以省去无用的请求;当然,为了用户友好性,你可以在显示名字时省略尾部斜杠。  


  例如,按如下的写法:

<a href=http://www.webjx.com/;; title="webjx.com">http://www.webjx.com<;;/a>

  技巧22:避免使用服务器端变量

  访问服务器端变量将使站点给服务器发送一个特殊请求,收集所有的服务器端变量,不仅仅是你所访问的那一个。
Jun 25
技巧1:将常用数据在Web服务器端缓存起来

  大部分的ASP页面都要从后台数据库中提取数据,然后将数据用HTML方式表现出来。
不管你的数据库多么快,从内存中提取数据总比从后台数据库中提取快;从本地硬盘中读取数据通常也比从数据库中快。因此,你可以通过在Web服务器端缓存数据来提高性能。  


  缓存是个典型的以空间换取时间的交易。如果你正确的缓存了数据,性能可能会突飞猛进。要想一个缓存能真正发挥效益,必须缓冲那些常用和计算复杂的数据。装满过期数据的缓冲区只能浪费内存。

  不经常变化的数据也是缓存的一个良好候选者,因为你可以不用关心同数据库中的数据保持同步。下拉列表框、引用表、小段DHTML代码,XML字符串、菜单项和站点配置变量(包括数据源名字(DSN),IP地址和Web路径)都是很好的缓存候选者。注意,不仅仅可以缓存数据本身,还可以缓存数据的表现。如果一个ASP页面很少变化,并且缓存代价比较高(比如,产品列表),可以考虑用静态HTML页面。

  技巧2:用Application对象或Session对象缓存常用数据

  ASP的Application和Session对象是一个极其方便的在内存中缓存数据的容器。你可以把数据放到Application或Session对象中,这些数据就会在整个HTTP调用中一直存在。每个用户有自己的Session对象中的数据,而Application对象中的数据可以在所有用户中共享。  


  应该在什么时候将数据装入Application或Session中呢?通常,数据在Application或Session启动的时候装入。要想在Application或Session启动的时候装入数据,需要分别在Global.asa的Application_OnStart()或Session_OnStart()中添加适当的代码;如果Global.asa中没有这两个函数,你可以手工添加。也可以在数据第一次使用的时候将其装入。要想这样,应该在ASP页面中写一些代码(或是写一个可重用的脚本函数)来检查数据是否存在并且如果数据不存在则将其装入内存。下面是一个经典的性能调整技术--Lazy Evaluation:

<%
Function GetEmploymentStatusList
 Dim d
 d = Application("EmploymentStatusList")
 If d = "" Then
   ' FetchEmploymentStatusList function (not shown)
   ' fetches data from DB, returns an Array
   d = FetchEmploymentStatusList()
   Application("EmploymentStatusList") = d
 End If
 GetEmploymentStatusList = d
End Function
%>

Similar functions could be written for each chunk of data needed.

In what format should the data be stored? Any variant type can be
stored, since all script variables are variants. For instance, you
can store strings, integers, or arrays. Often, you’ll be storing the
contents of an ADO recordset in one of these variable types. To get
data out of an ADO recordset, you can manually copy the data into
VBScript variables, one field at a time. It’s faster and easier to
use one of the ADO recordset persistence functions GetRows(),GetString
() or Save() (ADO 2.5). Full details are beyond the scope of this
article, but here’s a function that demonstrates using GetRows() to
return an array of recordset data:

' 获取记录集,返回数组
Function FetchEmploymentStatusList
 Dim rs
 Set rs = CreateObject("ADODB.Recordset")
 rs.Open "select StatusName, StatusID from EmployeeStatus", _
     "dsn=employees;uid=sa;pwd=;"
 FetchEmploymentStatusList = rs.GetRows() ' 将记录集用数组返回
 rs.Close
 Set rs = Nothing
End Function

A further refinement of the above might be to cache the HTML for the
list, rather than the array. Here’s a simple sample:

' 获取记录集,返回HTML Option列表
Function FetchEmploymentStatusList
 Dim rs, fldName, s
 Set rs = CreateObject("ADODB.Recordset")
 rs.Open "select StatusName, StatusID from EmployeeStatus", _
     "dsn=employees;uid=sa;pwd=;"
 s = "" & vbCrLf
 rs.Close
 Set rs = Nothing ' 释放rs
 FetchEmploymentStatusList = s ' 用字符串方式返回数据
End Function

  在正确情况下,你可以将ADO记录集本身缓存在Application或Session范围,但必须满足下面两个条件: .ADO必须被标记为自由线程模型(Free-threaded) .必须使用无连接记录集

  如果不能满足上面两个条件,一定不要缓存记录集。在下面的“不灵活的组件”和“不要缓存Connection”两个技巧中,我们将讨论在Application和Session中保存COM对象的危险性。

  当你在Application或Session中存储数据后,数据将一直保存,知道你的程序改变它,或是Session过期,或是Web服务重新启动。What if the data needs to be updated?手工刷新Application数据,可以调用只有管理员才可访问的用来刷新数据的ASP页面;或者定期的通过一个函数来周期性的更新数据。下面的例子在缓存数据中保存了一个时间戳,然后一段时间之后自动刷新数据。

<%
Const Update_INTERVAL = 300 ' 刷新间隔,单位是秒

'返回雇员状态列表
Function GetEmploymentStatusList
 UpdateEmploymentStatus
 GetEmploymentStatusList = Application("EmploymentStatusList")
End Function

'周期性的更新缓存中的数据
Sub UpdateEmploymentStatusList
 Dim d, strLastUpdate
 strLastUpdate = Application("LastUpdate")
 If (strLastUpdate = "") or _
    (Update_INTERVAL < DateDiff("s", strLastUpdate, Now)) Then

   ' Note: two or more calls might get in here. This is okay and
will simply
   ' result in a few unnecessary fetches (there is a workaround
for this)

   ' FetchEmploymentStatusList function (not shown)
   ' fetches data from DB, returns an Array
   d = FetchEmploymentStatusList()

   ' 更新Application对象时用Application.Lock()来保持数据一致性
   Application.Lock
   Application("EmploymentStatusList") = Events
   Application("LastUpdate") = CStr(Now)
   Application.Unlock
 End If
End Sub

  要知道在Session或Application中缓存大数组并不是一个太好的方法。在访问数组中的任何元素之前,脚本解释器都需要生成一个临时的整个数组的副本。例如,如果你缓存了一个100,000个字符串元素的数组,用来将邮政编码和当地的天气对应一一起来,在访问数组中任何一个字符串之前,ASP解释器首先必须复制所有的100,000个天气情况数据到一个临时数组中。在这种情况下,开发一个组件来储存天气情况数据或是使用词典(Dictioary)对象更为合适一点。不过,也不要因小失大,数组对象的的查找速度更快。索引一个词典比索引一个数组慢。你可以因你的情况而宜,选择合适的数据结构。

  技巧3:在硬盘上缓存数据和HTML页面

  有时,可能有太多的数据缓存在内存中。“太多”是个模糊的说法,它取决与Web服务器的内存大小、缓存项的数目和这些缓存项被访问的频度。无论如何,如果太多的数据在内存中缓存,可以考虑将数据用文本或XML文件缓存到Web服务器的硬盘上。可以将缓存到硬盘上和到内存中结合起来,针对你的站点,找到最优化的策略。  


  注意,当我们测量单一ASP页面的性能时,从硬盘上读取数据可能比从数据库中读取慢。但是,缓存能够减少数据库和网络的负载。在高负载的情况下,这将大大提高总体吞吐量。当被缓存的数据是非常复杂的查询,比如多表连接或是一个复杂的查询过程或一个非常大的记录集,缓存的效果将非常明显。

  ASP和COM提供了一些工具来建立基于硬盘的缓存方案。ADO Recordset对象的Save和Open方法可以保存和装入到磁盘上。还有一些用来访问文件的组件: .Scripting.FileSystemObject允许你创建、读取和写入文件。 .MSXML,同IE捆绑的微软的XML解释器,支持保存和装入XML文档。 .LookupTable对象是一个用来从磁盘装入简单列表的非常好的选择。

  最后,将数据表现缓存在硬盘上,比缓存数据本身要好。生成的HTML可以一个.htm或.asp文件保存在硬盘上;超连可以直接指向那些文件。你也可以用一些商业工具,如XBuilder和SQL Server互连网发布特性,来生成和处理HTML文件。另外,也可以用#include将HTML片段包含到ASP文件中;还可以用FileSystemObject来读取HTML文件。

  技巧4:避免在Application或Session对象中缓存COM对象

  虽然在Application或Session对象中缓存数据是一个好注意,但缓存COM对象可能带来严重的后果。在Application或Session对象中缓存常用COM对象非常诱人,但非常不幸,很多COM对象,包括那些用VB 6.0或早期版本写的组件,如果被缓存到
Application或Session对象中将会导致严重的性能瓶颈。  


  特别地,所有非Agile的组件被缓存到Session或Application中时,都将产生性能瓶颈。Agile组件是指聚合了Free-threaded marshaler(FTM)并且线程模型是Both
(ThreadingModel=Both),或线程模型是Neutral(Netural新出现在Windows 2000
和COM+中)的组件。下面的组件都是非Agile的:


  自由线程模型组件(除非他们聚合了FTM)
  Apartment线程模型组件
  单线程组件

  Configured组件(MTS/COM+库和服务包/应用)是非Agile的,除非它们是Neutral线程模型的。Apartment线程模型组件和其他非Agile组件最好是工作在页面范围内(就是说,他们在一个单一ASP页面中创建和销毁)。

  在IIS 4.0中,线程模型是Both的组件被看作是Agile的,但在IIS 5.0中,他们不再满足Agile的条件。组件线程模型必须是Both的,并且聚合了FTM,才被看作Agile的。如果试图将一个用Server.CreateObject创建的非Agile组件存储到
Application对象中时,IIS 5.0将会抛出一个错误。

  当ADO组件被标记为自由线程模型时,ADO记录集对象可以安全地存储。可以用Makfre15.bat,一般是放在\\program Files\Common\System\ADO这个文件夹里,将ADO组件标记为自由线程模型。有一点要注意:当用Access作后台数据库时,ADO不能被标记为自由线程模型。词典(Dictionary)组件也是Agile对象。

  技巧5:不要缓存数据库连接

  缓存ADO Connection对象是一个不好的策略。如果一个Connection对象被存储在
Application对象中并被所有页面使用,所有页面就会争着使用这个连接。如果
Connection对象被存储在Session对象中,就要为每个用户创建一个数据库连接,这就消减了连接池的作用,并且增大了Web服务器和数据库服务器的压力。可以用在每个使用ADO的ASP页创建和释放ADO对象来替代缓存数据库连接;因为IIS内建了数据库连接池,所以这种方法非常有效。  


  既然有连接的记录集保存了一个数据库连接的引用,因此也不应该在Application或Session对象中保存有连接的记录集。但是,你可以安全的缓存无连接的记录集,因为它并不包含到数据连接的引用。要想挂断一个记录集,可以采取如下两个步骤:

  Set rs = Server.CreateObject("ADODB.RecordSet")
  rs.CursorLocation = adUseClient ' 第一步

  rs.Open strQuery, strProv

  ' 将记录集同数据提供者和数据源挂断
  rs.ActiveConnection = Nothing  '第二步

  技巧6:正确地使用Session对象

  我们已经提到了在Application和Session中缓存数据的好处,下面我们将说一些
Session对象的缺点。在繁忙的站点上使用Session有一些不利的地方。繁忙是指这个站点每秒钟要处理数以百计的页面请求或同时连接数以千计的并发用户。这个技巧对那些必须要水平伸缩的站点--就是说,这些站点用多个服务器来实现负载平衡或容错--非常重要。对小的站点,如公司内网,Session相对与他消耗的资源来说,还是值得一用的。  


  ASP自动为每个访问Web服务器的拥护创建一个Session对象。每个Session大约消耗10K的资源,并使所有的请求都慢了一点。这个Session在超时周期内一直存在,这个周期一般是20分钟。对于Session来说最大的问题不是性能而是伸缩能力。Session不是跨Web服务器的;一旦一个Session在某个服务器上创建,它的数据都保存在那儿。
  这意味着如果你要在多个Web服务器环境中使用Session,你必须设计一套能使用户总是访问它的Session对象所在的Web服务器的策略;即将一个用户粘到一个Web服务器上。如果Web服务器崩溃,因为Session不是永久保存在磁盘上的饿,所以全部“粘”
  在其上的用户的Session状态都将丢失。实现“粘Session(sticky session)”的策略包括硬件和软件方案,如Windows 2000 Advanced Server中的Network Load Balancing和Cisco的Local Director。当然,这些方案并不完美,都要损失一些可伸缩性。Application对象也不是跨服务器的,如果你想在多服务器间共享和更新Application数据,你必须使用一个后台数据库。但无论如何,只读Application数据在多服务器环境中还是十分有用的。

  绝大多数任务优先(mission-critical)的站点都想在至少两台Web服务器上发布--如果没有比延长正常运行时间更重要的理由的话。因此,在设计阶段,你就要实现“粘Session”,或是简单地避免Session和其他将用户状态保存在一个独立Web服务器上的状态管理技术。

  如果不使用Session,就将它们关闭;可以通过Internet Service Manager(参看ISM文档)关闭你的应用的Session功能。如果决定使用Session,就要用一些方法将他们对性能的影响减到最小。可以将不需要Session的内容(如帮助窗口等)移到一个的关闭了Session的ASP应用中。如果某个单一页面不需要Session,可以将下面的语句放在页面的顶部来禁止Session功能:

<% @EnableSessionState=False %>

  使用该语句还有一个原因是Session在帧中会产生一个有趣的问题。ASP保证任何时候一个会话只有一个请求,这就导致如果浏览器同时请求多个页面,同一时刻将只有一个ASP请求能够访问Session;这避免了访问Session对象时产生的多线程问题;但很不幸,一个帧中的多个页面只能顺序的生成,一个接着一个,而不是兵法。用户可能会为多个帧等待较长时间。所以如果帧中的某个页面没有使用Session,就在页面顶部放置<% @EnableSessionState=False %>语句。

  作为使用Session对象的替代,还有很多其他的方法来管理会话状态。对小规模的状态(小于4KB),推荐使用Cookies,QueryString变量和隐藏表单变量。对大量数据,如购物信息,一个后台数据库可能是很好的选择。

  技巧7:将代码封装到COM对象中

  如果有很多VBScript或JScript代码,可以通过将代码封装到COM对象中来提高性能。
编译过的代码通常比解释代码运行得快。COM对象可以通过“前期绑定”来访问其他COM对象,这比脚本使用的“后期绑定”更高效。  


  下面是将代码码封装到COM对象中的优点(不仅仅是性能):

  COM对象可以很好地将商业逻辑同表现逻辑分离
  COM对象使代码可重用
  用VB,C或VJ写的代码比ASP代码更易调试

  COM对象也有不足,包括开发周期长和需要不同的编程经验等。有一点尤需注意,封装少量ASP代码可能在性能上适得其反;这种情况下,创建和调用COM对象的代价超过了编译代码性能上的好处。如何组合ASP代码和COM组件代码来产生最佳性能,往往是个令人头疼的问题。注意,同Windows NT 4.0/IIS 4.0相比,Windows 2000/IIS 5.0在脚本和ADO性能上已经大大提高。

  技巧8:对资源晚获取,早释放

  通常情况下,晚获取和早释放资源是最好的。这不仅适用于COM对象,也适用于文件句柄和其他资源。ADO连接和记录集是这项优化策略的主要对象。当使用完一个Recordset对象,应该立即将它释放,而不应等到页面结束。将VBScript变量设成Nothing是最好的方法。同时,释放相关的Command和Connection对象(别忘了在将Connection对象设成Nothing之前调用Close()方法)。

  技巧9:进程外执行以性能换取可靠性

  ASP和MTS/COM+都有选项让你来用可靠性换取性能。当建立和发布你的应用时,你应该理解这项交易的内幕。  


  ASP选项

  ASP应用有三种运行方法可选择。在IIS 5.0中,引入“分离级别(isolation level)”这个术语来描述这些选项。三种分离级别分别是:低(Low),中 (Medium)和高(High)。

  低分离级 这种级别被所有版本的IIS支持,并且速度也是最快的。它在Inetinfo.exe--主要的IIS进程--中运行ASP。如果ASP应用崩溃,IIS也将崩溃。

  (在IIS 4.0中,网管必须用诸如InetMon之类的工具来监视IIS,一旦IIS停止,运行批处理文件。IIS 5.0引入了“可靠的重启(reliable restart)”,会自动重新启动失败的服务器。

  中分离级 从IIS 5.0开始引入的新级别,指进程外运行,即ASP运行在IIS进程之外。 在中分离级中,所有的ASP应用共享一个进程空间。把多个进程外应用在同个空间中运行,减少了进程的树木。中分离级是IIS 5.0的默认级别。

  高分离级 IIS 4.0和IIS 5.0都支持。高分离级也是进程外的。如果ASP崩溃了,Web服务器并不崩溃。ASP应用会在下个ASP请求到达的时候自动重启。每个被配置为高分离级的ASP应用有自己的进程空间;这将每个ASP应用保护起来。它的缺点是对每个ASP应用需要一个分离的进程;这增加了许多资源消耗。

  哪种选项是最好的。在IIS 4.0中,进程外运行将使性能急剧下降;在IIS 5.0中,许多改进使进程外ASP应用的代价降到最低。事实上,在许多测试中,IIS 5.0中的ASP进程外应用比II4 4.0中的进程内运行都快。但无论如何,在任何平台上,还是进程内(低分离级)运行能带来最佳的性能。然而,在相对低点击率或低最大吞吐量的情况下,低分离级不会带来任何益处;因此,除非每个Web服务器需要应付成千上百的页面请求,不然你不会需要用低分离级。通常,需要在多个配置下进行测试,才能决定使用哪种配置。

  注意:当在进程外运行ASP应用时(中或低分离级),ASP应用运行在NT 4上的MTS中或Windows 2000的COM+中;就是说,在NT4中,ASP应用运行在Mtx.exe中;在
Windows 2000中,ASP应用运行在DllHost.exe中。你可以在任务管理器中看到这些进程在运行。

  COM选项

  COM组件也有三种配置选项,但不完全对应于ASP的选项。COM组件可以是“无配置的(Unconfigured)”、作为一个库应用(Library Application)或是作为一个服务应用(Server Application)。“无配置的”意味着组件不注册到COM+中,组件将在调用者进程空间中运行;即“进程内”。库应用也是进程内的,但可以从COM+的服务,如安全、事务和上下文支持,中获益。服务应用则被配置成运行在自己的进程空间内。

“无配置”比库应用有一点优越性;而库应用比服务应用在性能上更优越。这是因为库应用和ASP是在同一个进程内的,而服务应用是运行在自己的进程空间里的。进程间调用比进程内调用的代价高。同样,在进程间传递如记录集这样的数据,需要在两个进程间复制所有的数据。

  缺陷!当使用COM服务应用时,要想在ASP和COM间传送数据,必须保证对象实现了“按值排列(marshall-by-valu)”,或者说MBV。实现了MBV的对象将自身从一个进程复制到另一个进程。这比下面的方法好:对象留在创建者进程,其他进程重复调用创建进程来使用对象。无连接ADO记录集是MBV,有连接记录集就不是。

  Scripting.Dictonary对象没有实现MBV,不能在进程之间传递。最后,对VB程序员的一个提示:MBV不是通过用ByVal来传递参数。MBV是原始组件作者实现的。

  怎样做?
  推荐的用可靠性换取性能的配置:

  在IIS 4.0上,用ASP的低分离级,并使用MTS服务包。
  在IIS 5.0SHANG,用ASP的中分离级,使用COM+的库应用。

  技巧10:使用Option Explicit

  在.asp文件中使用Option Explicit。该指示放在.asp文件的顶部,强制开发者在使用任何变量之前必须定义它。许多程序员认为这有助于调试程序,因为它消除了打字错误的可能(如将MyXMLString=敲成MyXLMString=)  


  另外一点可能更加重要:已定义变量比未定义的变量快。ASP每次是用名字来引用未定义变量的;而另一方面,每个已定义变量有一个序号,ASP用这个序号来引用已定义变量。既然Option Explicit强制变量定义,就保证了所有的变量都是已定义的,访问速度就更快了。

  技巧11:在子过程和函数中使用本地变量

  本地变量是那些在子过程和函数中定义的变量。在函数和子过程中,访问本地变量比访问全局变量更快。使用本地变量也使代码更干净,因此尽量使用本地变量吧。
分页: 8/25 第一页 上页 3 4 5 6 7 8 9 10 11 12 下页 最后页 [ 显示模式: 摘要 | 列表 ]