分类存档: Web - 第2页

YUI学习笔记(4)

YUI学习笔记(4)

by xujiwei (http://www.xujiwei.com/)

YAHOO.util.Subscriber 与 YAHOO.util.CustomEvent。

1. YAHOO.util.Subscriber (event.js)

这 应该算是设计模式中的观察者模式了,Subscriber 订阅一个事件,在 Publisher 触发那个事件后,会逐个通知 Subscriber。

对 于一般开发者来说,并不需要去关心 Subscriber 的实现,因为 Subscriber 主要是 CustomEvent 用来分发动作以及删 除 Subscriber 的。

Subscriber 类只定义了 3 个属性:fn、obj 以及 override,3 个方 法:getScope、contains、toString,其中 fn 为订阅者的回调函数,obj 为要传递给回调函数的一个额外参 数,override 如果是布尔型的 true 值,那么表示使用 obj 属性为回调函数执行时的上下文,或者直接使用一个对象来作为回调函数执行的 上下文。

Subscriber 的 3 个方法中比较有用的是 getScope 和 contains,toString 只是简单的 将 Subscriber 对象转换成一个字符串。getScope 会根据 Subscriber 对象的 override 属性来获取回调函数执行 的上下文,contains 用来判断 Subscriber 对象与指定的回调函数和 obj 是否一致。

2. YAHOO.util.CustomEvent (event.js)

CustomEvent 的 作用相当在观察者模式中发布者的身份,可以通过它来实现一个自己的事件发布者。

CustomEvent 构造函数的定义如下:

CustomEvent = function(type, oScope, silent, signature)

在 创建 CustomEvent 对象时,几个参数的用途如下:

type 是自定义事件的名称,在使用回调函数的参数格式 为 YAHOO.util.Event.LIST 时,回调函数的第一个参数就是 CustomEvent 对象的名称;

oScope 是 执行回调函数时的上下文对象,也就是在回调函数中可以用 this 来引用这个对象;

silent 参数是用指示是否 在 YUI 为 debug 版本时禁用调试信息;

signature 用来指示回调函数参数的格式,可以为 YAHOO.util.Event.FLAT 或 YAHOO.util.Event.LIST, 默认是 YAHOO.util.Event.LIST。

在使用 CustomEvent 之前,先要了解一下 CustomEvent 中 回调函数参数的格式,CustomEvent 的回调函数可以有两种格式,一种为 YAHOO.util.Event.LIST,这种格式的回调函数具有 三个参数,分别是事件名称、参数数组和附加对象参数;另外一种回调函数参数格式为 YAHOO.util.Event.FLAT,这个时候回调函数只有两 个参数,一个为 CustomEvent 对象调用 fire 方法时的第一个参数,另外一个是订阅时的额外对象参数。

在创 建 CustomEvent 对象时,CustomEvent 构造函数还会首先创建一个内部的自定义事件,用来处理该自定义事件被订阅的事件,这 在 EventProvider 中用到,这里暂且不提。

CustomEvent 对象使用一个名为 subscribers 的数组来保 存所有订阅者的列表,并且通过维护这个列表来维护该自定义事件的订阅者。

CustomEvent 对象提供了 subscribe、 unsubscribe、unsubscribeAll、fire 这几个方法来处理自定义事件的订阅、退订以及触发等动作,而这几个就是观察者模式中的 主要动作了。

subscribe 的签名为 subscribe: function(fn, obj, override), 三个参数分别对应了 Subscriber 类构造函数的三个参数,分别对应了回调函数、额外对象参数以及是否使用额外对象参数作为执行上下文。 subscribe 只是简单的判断参数 fn 是否有定义,然后会触发自定义事件订阅事件,最后使用这三个参数创建一个 Subscriber 对象添 加到 CustomEvent 对象的 subscribers 属性中。

unsubscribe 方法用来取消事件的订阅,它的函数签名 为 unsubscribe: function(fn, obj),两个参数分别是回调函数和额外对象参数,如果使用无参数调 用 unsubscribe 方法,那么会直接调用 unsubscribeAll 来删除所有订阅者,否则会逐一判断 subscribers 中的每 个对象,通过使用 Subscriber 对象的 contains 方法来判断给定的 fn 和 obj 与其是否一致,如果一致,就使用一个私有方 法 _delete 来删除这个 Subscriber。

unsubscribeAll 方法没有参数,它只是简单的直接删除自定义事件的 所有订阅者,最后直接给 subscribers 赋值一个空数组来避免有可能出现漏删订阅者的情况。

内部方法 _delete 的参数 是 Subscriber 对象在 subscribers 数组中的索引,它会先删除 Subscriber 对象的 fn 和 obj 属性,最后使 用 splice 方法将 Subscriber 对象从数组中删除。

var s = this.subscribers[index];
if (s) {
delete s.fn;
delete s.obj;
}
this.subscribers.splice(index, 1);

使用 delete 删除 Subscriber 对象的 fn 和 obj 属性是为了去除 回调函数及额外对象参数的引用,以免引起不必要的内 存泄露。

CustomEvent 对象最重要的方法就是 fire 了,就是通过这个方法来通知所有了订阅者这个自定义事件被触发了。 fire 方法先使用 Array 的 slice 方法将调用 fire 方法时的参数转化成数组,这样就可以在调用 Subscriber 的回调函 数时可以传递参数给它们。

在遍历 subscribers 中的 Subscriber 前,fire 方法先使用 了 subscribers 的 slice 方法来创建一个 subscribers 的副本,这样避免在执行 fire 的过程中 有 Subscirber 取消订阅了这个自定义事件会导致错误。

在执行 Subscriber 的回调函数前,先使 用 Subscriber 对象的 getScope 方法来获取执行回调函数时的上下文对象,再根据 CustomEvent 对象 的 signature 属性来决定怎么去调用 Subscriber 的回调函数。

如 果 signature 为 YAHOO.util.CustomEvent.FLAT,那么就把调用 fire 方法时的第一个参数做为回调函数的第一 个参数,再把 Subscriber 对象的 obj 属性做为第二个参数:

s.fn.call(scope, param, s.obj)

如 果 signature 为 YAHOO.util.CustomEvent.LIST,那么就除了把整个 fire 方法的参数列表传递给回调函数外, 还要传递当前 CustomEvent 的名称给回调函数:

s.fn.call(scope, this.type, args, s.obj)

Subscriber 的 回调函数如果在执行过程中出现了错误,那么 CustomEvent 的 lastError 属性就是指向错误对象的引用,另外,如 果 YAOO.util.Event.throwErrors 为 true,那么会把这个错误再次抛出。

另外,Subscriber 对 象也可以控制事件通知是否继续,如果 Subscriber 对象的回调函数执行后的返回一个 false,那么在 fire 方法中就会停止通知剩下 的 Subscriber 对象,通常情况下,先订阅自定义事件的 Subscriber 可以阻止后订阅的 Subscriber 接收到通知。

使 用 YUI 的自定义事件(CustomEvent)可以很方便地实现观察者模式,更好地组织 JavaScript 程序的结构。

YUI学习笔记(3)

YUI学习笔记(3)

by xujiwei (http://www.xujiwei.com/)

YAHOO.lang.later,YAHOO.lang.trim,YAHOO.lang.isXXX 以及 YAHOO.lang.hasOwnProperty。

1. YAHOO.lang.later(yahoo/yahoo.js)

later 方法用来延迟执行方法,是对 setInterval 和 setTimeout 的封装,并且可以传递参数到延迟执行的函数或者使用参数数组批量执行指定的函数。

later 方法的签名为:

later: function(when, o, fn, data, periodic)

when 是用来指定在多长时间后执行指定的函数,以毫秒计算;

o 是上下文对象,即在要执行的函数里使用 this 是会引用这个 o 对象;

fn 就是要延迟执行的函数了,可以传递一个函数引用,也可以传递一个字符串,later 方法会在 o 对象中查找对应名称的属性来做为要执行的方法;

data 就是传递给延迟执行的函数的参数了,可以为一个参数,或者是一个参数数组,那么如果我们要传递的参数本身就是一个数组的话,就要自己先把这个参数数组包装成一个数组;

peridoic 参数是一个布尔值,用来表示延迟执行的函数是否需要周期执行而不是只执行一次。

later 方法执行后会返回一个对象,包含了一个名 interval 的属性用来表示函数是以 setInterval 来执行的还是以 setTimeout 来执行的,以及一个方法 cancel 用来取消执行被延迟执行的函数。嗯,不过只有在 peridoic 为 true 时这个 cancel 方法比较有用,毕竟如果 peridoic 为 false 时函数执行一次就不会再执行了,cancel 也没有什么意义。

{
interval: periodic,
cancel: function() {
if (this.interval) {
clearInterval(r);
} else {
clearTimeout(r);
}
}
};

    2. YAHOO.lang.trim (yahoo/yahoo.js)

    trim 方法用来去除字符串两边的空白字符,其实也就用了一个正则来匹配字符串两端的空白字符并替换成空白字符串。

    不过在 YAHOO.lang.trim 中,它使用了一个 try … catch 来在调用的参数不为字符串时直接返回原来的对象。

    3. 对象类型判断 YAHOO.lang.isXXX (yahoo/yahoo.js)

    YAHOO.lang 中包含了一堆用于判断对象是否为某个类型的方法,例如 isObject、isString、isNumber 等。

    YAHOO.lang.isArray 用来判断一个对象是否为数组,YUI 中并不是使用的 obj instanceof Array 或者 obj.constructor == Array,而是判断指定的对象是否有两个数组应该具有的经典属性和方法 length 和 splice,这是因为如果要判断的对象是属于另外一个 frame 中时,除非你能获得另外一个 frame 中 Array 定义的费用,否则 instanceof 和 constructor == Array 都是返回 false 的,我们可以使用以下代码来测试一下:

    test.html

    <iframe id=”frame” src=”iframe.html”></iframe>
    <script type=”text/javascript”>
    <!–
    var f = document.getElementById(‘frame’);
    setTimeout(function() {
    alert(f.contentWindow.arr.constructor == Array);
    }, 1000);
    //–>
    </script>

      iframe.html

      <script type=”text/javascript”>
      <!–
      var arr = [1, 2, 3];
      //–>
      </script>

        而如果把 Array 换成 arr 所在 frame 中 Array 的引用,结果就是为 true 了:

        test.html

        <iframe id=”frame” src=”iframe.html”></iframe>
        <script type=”text/javascript”>
        <!–
        var f = document.getElementById(‘frame’);
        setTimeout(function() {
        // 这里将 Array 换成 f.contentWindow.Array
        alert(f.contentWindow.arr.constructor == f.contentWindow.Array);
        }, 1000);
        //–>
        </script>

          但是因为 isArray 是一个通用的方法,在使用时不可能要求调用者同时也传递一个目标 frame 的引用过来,所以只好通过判断两个经典的属性来代替了。

          YUI 的注释中提到是 Safari 不支持跨框架通过 instanceof 的检测,但是我测试的结果是几个主流浏览器都不支持跨框架的 instanceof 检测。

          YAHOO.lang.isBooleanYAHOO.lang.isFunctionYAHOO.lang.isStringYAHOO.lang.isUndefined 这 4 个方法是直接通过使用 typeof 来判断对象类型是否为 boolean、function、string 或 undefined 的。

          YAHOO.lang.isNumber 也是通过 typeof 来判断的,不过附加了一个检测条件,使用 isFinite 来检测数字是否为有限的,即不是 NaN、负无穷或正无穷。

          YAHOO.lang.isObject 通过 typeof 来判断对象类型是否 object 以及使用 isFunction 方法来检测对象是否为一个 Function,在 YAHOO.lang.isObject 方法的眼中,Object 与 Function 都是 Object。

          YAHOO.lang.isNull 直接将判断对象与 null 进行严格相等比较。

          YAHOO.lang.isValue 用来判断传递给它的参数是否为一个有值的对象,它是将 isObject、isString、isNumber 以及 isBoolean 四个方法对参数进行判断的结果进行或操作来作为结果的,对 null/undefined/NaN 这三个值进行 isValue 判断时就会返回 false,因为 0、false、’ 这三个值在分别在 isNumber、isBoolean、isString 时判断为 true,所有 isValue 的结果也为 true。

          4. YAHOO.lang.hasOwnProperty (yahoo/yahoo.js)

          嗯,这个方法也是对 Object.prototype.hasOwnProperty 的封装,前提是当前上下文的 Object.prototype 有 hasOwnProperty 方法,如果 Object.prototype.hasOwnProperty 不存在,那么 YAHOO.lang.hasOwnProperty 就实现了自己的 hasOwnProperty 方法,它是通过判断对象的属性与对象 prototype 的同名属性是否为同一个对象的判断的。如果对象的属性不是从原型继承过来的,那么两个属性值就有可能不一致:

          o.constructor.prototype[prop] !== o[prop]

          不过这里也有个问题,在 YAHOO.lang.hasOwnProperty 的注释中也提到了,就是 YUI 自己实现的 hasOwnProperty 是无法分辨在属性值一样时,这个属性是从原型继承过来的还是对象自身的:

          var YAHOO = {
          lang : {
          isUndefined : function(o) {
          return typeof o === “undefined”;
          },
          hasOwnProperty : function(o, prop) {
          return !this.isUndefined(o[prop]) &&
          o.constructor.prototype[prop] !== o[prop];
          }
          }
          }
          var A = function() {};
          A.prototype.foo = ‘foo’;
          var a = new A();
          a.foo = ‘foo’;
          alert(a.hasOwnProperty(‘foo’)); // true
          alert(YAHOO.lang.hasOwnProperty(a, ‘foo’)); // 这里返回 false,事实上应该是 true

            但是现在 Object.prototype 中没有 hasOwnProperty 的浏览器应该基本没有了,所以这个问题也不是很大。

            5. YAHOO.lang.augmentProto 以及 YAHOO.lang.extend 的别名 (yahoo/yahoo.js)

            在 yahoo.js 中,将 YAHOO.lang.augment 以及 YAHOO.augment 指向了 YAHOO.lang.augmentProto,YAHOO.extend 指向了 YAHOO.lang.extend,可以一定程度上减少代码的编写。

            YUI学习笔记(2)

            YUI学习笔记(2)

            by xujiwei (http://www.xujiwei.com/)

            YAHOO.lang.dump 与 YAHOO.lang.substitute。

            1. YAHOO.lang.dump(yahoo.js)

            dump 方法用来将一个对象转储为一个字符串,并且可以指定转储的深度。

            在 dump 过程中,对于基础类型例如 Number、String、Boolean,是直接返回字符串的,对 HTMLElement 对象是返回 HTMLElement 本身,也就是不做处理,对于函数 Function 则是返回字符串“f(){…}”。

            对于数组,dump 返回的格式就如我们定义时一样“[item1, item2 item3, …]”,对于对象 Object,则是使用键值对的形式“key => value”,与 PHP 里面的数组定义方式相似。

            例如一个对象定义如下:

            var obj = {
            num: 1,
            str: “string”,
            bool: true,
            date: new Date(),
            obj: {
            obj_num: 1,
            obj_str: “obj_string”
            },
            foo: function() {
            }
            }

              dump 之后的字符串如下:

              {num => 1, str => string, bool => true, date => Wed Jan 7 15:57:52 UTC+0800 2009,obj => {obj_num => 1, obj_str => obj_string}, foo => f(){…}}

              字符串没有被引号引起来,这个方法只适合用来展示对象的结构,与 JSON 序列化差得有点远了。

              2. YAHOO.lang.substitute(yahoo.js)

              substitute 实现的功能与 C# 中的 String.Format 方法类似,用来格式化一个字符串,但是它是一个字符串来代表一个占位符,而不像 C# 中是使用数字,或许这个用法和 python 中的字符串格式化更像。

              substitute 调用的格式为:substitute(formatString, valueObject [,formatCallback]),其中 formatString 是作为格式化字符串的字符串,其中包含着一些格式为 {key} 这样的占位符,valueObject 是包含要取代占位符的值的对象,它的结构为 { key1 : value1, key2 : value2 },最后的 formatCallback 参数则是额外的格式化处理函数,用来在格式化字符串进行一些额外的处理。

              substitute 格式化字符串的占位符格式为“{key [meta]}”,其中 meta 是可选的,用来表示额外的格式化属性,而要替换的数据则是 valueObject 中名称为占位符 key 的属性,例如,使用 { name : “xujiwei” } 做为数据,那么“{name}”将被替换为“xujiwei”。

              基本数据类型例如 String、Number 在 substitute 中是直接替换的,但是如果要替换的值为一个数组,那么 subtitute 会先使用 YAHOO.lang.dump 方法将对象转储为一个字符串再进行替换,深度为 10,而在替换的值为一个对象时,则会先检测 meta 数据中是否包含了 dump 关键字,或者这个对象的 toString 方法是否与 Object 对象原型的 toString 方法一样,如果是的话就使用 YAHOO.lang.dump 方法来将对象转储为字符串进行替换,否则就调用对象的 toString 方法得到字符串进行格式化。

              如果替换的数据类型不是 Object、Array、String、Number 中的一种的话,那么这个占位符就不进行替换。

              一个覆盖了所有类型数据的例子如下:

              YAHOO.lang.substitute(
              “String : {key1}\nNumber : {key2}\nObject : {key3}\nArray : {key4}”,
              {
              key1 : “xujiwei”,       // String
              key2 : 123456,          // Number
              key3 : {
              firstName : “Jiwei”,
              lastName  : “Xu”
              },                      // Object
              key4 : [1, 2, 3]        // Array
              });

                它的输出如下:

                String : xujiwei

                Number : 123456

                Object : {firstName => Jiwei, lastName => Xu}

                Array : [1, 2, 3]

                继续阅读 »

                YUI学习笔记(1)

                YUI学习笔记(1)

                by xujiwei (http://www.xujiwei.com/)

                今天开始学习 YUI,加强一下对 JavaScript 的理解。

                1. 命名空间 YAHOO.namespace(yahoo.js)

                YUI 中使用了命名空间的概念,在 JS 中使用命名空间是为了模块以及代码组织清晰的需要,通过使用命名空间可以将功能相似或同一模块中的函数、变量等放到同一个命名空间下。

                其实 JS 中的命名空间就是一个嵌套的对象而已,即子命名空间相当于父命名空间中的一个属性,它本身也是一个对象,这样子命名空间也可以有自己的子命名空间。

                在 YUI 中,命名空间的格式与 C# 中类似,是以点号分隔的字符串,可以使用 YAHOO 对象的静态方法 namespace 来创建命名空间,需要注意的是,以 namespace 方法创建命名空间时,所有的对象都是附加在 YAHOO 这个对象上的,如果调用 namespace 方法创建一个“com.xujiwei.ajax”这样的命名空间,其中 ajax 的完整路径将是 YAHOO.com.xujiwei.ajax,而不是 com.xujiwei.ajax,也就是说 namespace 不会产生新的顶层对象,一切以 YAHOO 对象为基础。

                namespace 方法接受一个或多个字符串来生成命名空间,但是它只返回最后一个参数所代表的命名空间的最后一个域的对象,例如使用 namespace(“com.xujiwei.js”, “com.xujiwei.ajax”) 时它的返回值是代表 js 这个模块的的对象:

                YAHOO.namespace(“com.xujiwei.js”, “com.xujiwei.ajax”).get = function() {
                alert(“get method of com.xujiwei.ajax”);
                }
                YAHOO.com.xujiwei.ajax.get();

                2. YAHOO.lang.extend(yahoo.js)

                YAHOO.lang.extend 负责扩展一个现在的函数对象,相当于面向对象中的继承,并且可以附加一个参数重写继承的方法,它的函数声明是:

                extend: function(subc, superc, overrides)

                其中 subc 为要扩展的函数对象,superc 是要继续的函数对象,overrides 中包含着要重写父类的方法。

                YAHOO.lang.extend 是通过原型链的方式来扩展对象,即创建一个父类的实例做为子类的原型对象,在 extend 方法中,首先构造了一个空函数,将空函数的 prototype 指向父类的 prototype,以免在父类的构造函数需要参数时创建父类实例时会出错,然后创建这个空函数的实例赋值给子类的 prototype 对象,并且给子类添加一个 superclass 属性指定它的父类,另外还指定了子类 prototype 的 constructor 属性为子类构造函数,以免创建子类实例时会丢失 constructor 属性,这个问题我在《慎用 somefunction.prototype》有提到。

                var F = function() {};
                F.prototype = superc.prototype;
                subc.prototype = new F();
                subc.prototype.constructor = subc;
                subc.superclass = superc.prototype;

                接着处理的是要重写的函数,首先遍历 overrides 参数,并且使用 hasOwnProperty 方法判断属性是否为 overrides 自身的属性,而不是从 Object 对象的 prototype 继承过来的,如果是 overrides 自身的属性,那么就覆盖子类 prototype 的相同成员。

                在处理 overrides 的最后,还修复了 IE 中不枚举与内部函数同名的函数的 bug,即例如 overrides 中包含了 toString 或 valueOf 的定义,但是这两个函数也是 Object 对象所具有的,在 IE 中使用 for(p in overrides) 就不会得到这两个函数,因此需要修复,这是 YAHOO.lang 中 _IEEnumFix 函数所做的工作。

                if (overrides) {
                for (var i in overrides) {
                if (L.hasOwnProperty(overrides, i)) {
                subc.prototype[ i ]=overrides[ i ];
                }
                }
                L._IEEnumFix(subc.prototype, overrides);
                }

                _IEEnumFix 只是简单的判断当前浏览器是否为 IE,如果为 IE 则从一个已经定义的列表中获取在 IE 中枚举不到的函数名列表,逐一判断是否为源对象自身的属性并且与 Object 原型中的同名函数不一样,如果是则添加到目标对象。如果浏览器不是 IE 则 _IEEnumFix 直接就是一个空函数,直接不处理。

                _IEEnumFix: (YAHOO.env.ua.ie) ? function(r, s) {
                for (var i=0;i<ADD.length;i=i+1) {
                var fname=ADD[ i ],f=s[fname];
                if (L.isFunction(f) && f!=Object.prototype[fname]) {
                r[fname]=f;
                }
                }
                } : function(){},

                3. YAHOO.lang.augmentObject(yahoo.js)

                augmentObject 方法用来引入目标对象的属性,并且处理重写,通常用于合并配置对象与默认配置对象。

                augmentObject 有三种调用方法,分别是:

                augmentObject(result, source),一般情况下用些方法,从 source 引入 result 中没有的属性;

                augmentObject(result, source, override),override 设置是否覆盖,如果是 true 的话,那么 source 中所有属性会覆盖 result 中已有的属性;

                augmentObject(result, source, ovrride_name, …),指定要覆盖属性的列表,source 参数后为要覆盖属性名称的列表,例如覆盖 result 中的 toString 和 valueOf,那么可以调用 augmentObject(result, source, “toString”, “valueOf”)来实现。

                合并对象的最后同样调用了 _IEEnumFix 方法来修正 IE 中不枚举与内部属性同名属性的 bug。

                augmentProto 与 augmentObject 功能基本相同,只不过后者是处理对象,而前者是处理函数的 prototype,它是通过使用两个参数的 prototype 来构造一个参数列表,传递给 augmentObject 对象来实现 augmentProto 的功能。

                var a=[r.prototype,s.prototype];
                for (var i=2;i<arguments.length;i=i+1) {
                a.push(arguments[ i ]);
                }
                L.augmentObject.apply(this, a);

                因为 augmentProto 是调用 augmentObject 来实现的,因此它的调用格式也是 augmentObject 一样,具体可以看看上面 augmentObject 的三种调用方法。

                另外,YAHOO.lang.merge 方法也是通过调用 augmentObject 方法来实现对象的合并。

                Learn HTTP 1.1 Status Code Part.5

                Learn HTTP 1.1 Status Code Part.5

                by xujiwei(http://www.xujiwei.com/)

                前一篇介绍了 4xx 系列状态码 中 406 到 417,这篇接着讲剩下的 5xx 系列。

                7. 5xx 系列内部服务器错误

                在 服务器处理请求时出问题了,服务器可以发一个 5xx 系列错误码给客户端,表示服务器在处理请求的时候出问题了,问题是出在服务器身上而不是客户端身 上。另外,服务器如果发送了 5xx 系列的状态码,除非客户端是使用的 HEAD 方法,否则服务器还应该在响应中给出错误的描述、原因以及解决方法 等,客户端可以把这些东西给用户看,让用户知道是什么原因导致了请求出错。

                不过通常情况下,用户在收到 5xx 系列错误码的同时,是不会 收到导致错误的详细技术信息的,这是为了保护服务端程序安全的需要。服务器一般会返回一个页面描述所出的错误是由什么原因引起的,而不涉及详细技术信息。 例如在 ASP.NET 中,可以在 web.config 中的 customErrors 节配置自定义错误的显示方式,有 On、Off、 RemoteOnly 三种方式,可以根据具体的需要来设置,像 RemoteOnly 就会给本地开发者显示详细的错误信息以及调试信息,而对远程访问 者则会只显示服务器运行时出现错误,没有详细的调试信息。

                7.1. 500 Inernal Server Error

                500 Internal Server Error 表 示内部服务器错误,这个对于一般用户来说是比较常见的,许多 IIS 服务器都会配置成不发送详细错误信息,而 IIS 的默认设置就是在服务器发生内部 错误时发送 Internal Server Error 这个字符串代替具体的错误信息。

                对于 ASP 程序来说,如果是使 用 VBScript 作为脚本语言,那么可以在代码的最前面加一句 On Error Resume Next 来忽略错误继续运行程序,或者加一个错 误处理子程序,或者直接停止脚本的运行,以防止关键信息的泄露。

                7.2. 501 Not Implemented

                501 未 实现表示服务器对于客户端请求的方法没有实现,从而不能满足客户端的请求。这个错误比较少见,我目前还没有见过这个错误,或者在使用一个开发中 的 Web 服务器时会出现这个错误,例如使用 PUT 方法发送请求但是服务器不支持 PUT 方法,那么服务器就可以发 送 501 Not Implemented 错误码。

                7.3. 502 Bad Gateway

                当 服务器是作为一个网关或者代理时,在处理客户端请求时从上游服务器收到了一个无效的响应,这时服务器可以返回一个 502 Bad Gateway。

                出 现这种情况可能的原因有网关或代理过滤了所请求的网址,或者真正的服务器挂掉了,这些时候代理或网关都可以发送 502 Bad Gateway。

                那 么,502 Bad Gateway 是用来表示代理或网关在处理请求时发生了错误,并不一定是原始服务器出现了问题。

                7.4. 503 Service Unavailable

                503 服 务不可用,表示服务器当前负载过大或者正在维护,这通常表示这是一个临时的情况,服务器会在一定的时间之后恢复正常。如果时间已知,例如服务器在维护,那 么服务器在发送 503 Service Unavailable 的同时,可以在响应头部添加一个 Retry-After 字段表示维护会持续多久, 在指定的时间之后,客户端就可以尝试再次发送请求了。

                如果是使用虚拟主机,那么在网站负载过大的时候,一般都会出 现 503 Service Unavailable,因为提供商会限制 CPU 使用率 :)

                不过 503 并不一定表示服务器负载过大 或者正在维护,也可能是服务器只是想拒绝处理请求而已。那么,我们也可以在处理 IP 屏蔽之类的情况时直接来一 个 503 Service Unavailable :)

                7.5. 504 Gateway Timeout

                与 502 Bad Gateway 类 似,这个状态码一般是代理或网关服务器返回的,出现这个状态码意味着代理服务器在处理请求时超过了超时时间仍个没有从原始服务器接收到响应内容,也有可能 是因为在解析原始服务器的域名时超时了。

                7.6. 505 HTTP Version Not Supported

                客 户端使用 HTTP 版本不被服务器支持,或者服务器拒绝支持客户端所使用的 HTTP 版本,使用向一个只支持 HTTP 1.0 的服务器发送了一个 使用 HTTP 1.1 的请求,那么服务器可以响应 505 HTTP Version Not Supported。在 RFC 指出服务器如果发送 了 505 HTTP Version Not Supported 状态码,那么同时也应该在响应中指出为什么客户端所用的 HTTP 版本不被支持以 及服务器支持哪些协议的哪些版本。

                后记

                这是本系列的最后一篇了,发现大部分东西都是直接 翻译了 RFC 中的内容,不过也算是把所有的 HTTP 状态码学习了一遍,对于 AJAXRequest 的开发也有了点想法,但是发 现 jQuery 中对 HTTP 状态的处理还算全面,突然觉得 AJAXRequest 有些简陋了。嗯,不过 AJAXRequest 也是我学习 的一个过程,敝帚自珍,哈~

                参考资料

                1. HTTP Error 502 – Bad gateway

                系 列目录

                1. Learn HTTP 1.1 Status Code Part.1 : Successful 2xx
                2. Learn HTTP 1.1 Status Code Part.2 : Redirection 3xx
                3. Learn HTTP 1.1 Status Code Part.3 : Client Error 400-405
                4. Learn HTTP 1.1 Status Code Part.4 : Client Error 406-417

                Learn HTTP 1.1 Status Code Part.4

                Learn HTTP 1.1 Status Code Part.4

                by xujiwei(http://www.xujiwei.com/)

                前一篇介绍了 4xx 系列状态码 中 400 到 405,这篇接着讲剩下的 4xx 系列。

                6.7. 406 Not Acceptable

                客 户端在收到 406 Not Acceptable 状态码时,一般是因为服务器在处理客户端请求时,找不到对应客户端发送过来的请求头部 的 Accept 字段中列举的响应类型的响应内容,例如使用 Ajax 时,如果通信数据格式选择 json,发送请求时头部 Accept 字段一般 会设置为 application/json,但是如果服务端并不支持 json,那么服务端可以发送一个 406 Not Acceptable,同时 也应该在 Content-Type 字段中指定客户端所请求的资源的格式,这时客户端可以考虑使用另外的格式来接收响应。

                不过备注中说 到,如果没有合适的资源来响应客户端的请求,HTTP 1.1 服务器也可以发送一个响应。当然发送 406 Not Accepteble 是一个更好 的选择,这样客户端就能知道什么格式的数据才能是服务器所能接受的。

                6.8. 407 Proxy Authentication Required

                通 常情况下,这个状态码应该是代理服务器发过来的,表示客户端首先应该进行代理服务器的认证才能继续完成请求,在 IE 里可能会表现为提示用户输入代理服 务器的用户名和密码,如果选择取消则会提示“您必须通过代理的身份验证,然后 Web 服务器才能处理您的请求。”。

                6.9. 408 Request Timeout

                请 求超时,出现这个状态码时,有可能是服务端的问题,也有可能是客户端的问题(呃,有点废话……)。但是,可能在一些 Web 服务器中,并不会在运行超时 时返回 408 Request Timeout。例如在 ASP 中,如果运行一个超长时间的脚本,IIS 并不会发 送 408 Request Timeout 状态码,而是发送一个 500 Internal Server Error,所以如果在写一个程序时,如 果运行时间已经超过了预期,那么可以自己结束程序运行并向客户端发送一个 408 Request Timeout。

                在 HTTP 1.1 状 态码的定义中,如果客户端收到一个 408 Request Timeout 状态码,可以在过一段时间后重新发送这个请求。因此,如果是因为客户端的原 因导致的脚本超时,例如客户端网络速度太慢等,推荐发送一个 408 Request Timeout 状态码,但是如果是由服务器的原因,例如不能处理 过大的文件,则应该直接给出错误,而不是发送一个 408 Request Timeout。

                6.10. 409 Conflict

                409 Conflict 表 示服务器在处理客户端发送的请求时发生了冲突,和 403 Forbidden、404 Not Found 等一样,服务器应该同时也给出出问题的原因 或解决方法。

                很可能发生 409 Conflict 错误的时候是使用 PUT 方法发送请求时,如果所请求的资源已经被第三方修改过,这 时就需要解决两次修改之间的冲突问题。

                这个状态码可以与 SVN 中的冲突一起理解,在 SVN 中,如果你修改一个文件的工作副本,在提 交之前又有另外一个人修改并提交了同一个文件的副本,这时就需要解决两个版本之间的冲突,例如合并两个版本之间的不同之处等。

                6.11. 410 Gone

                如 果客户端请求的资源已经永久删除了,并且不知道有什么其他的资源可以代替它,那么服务器就会发送一个 410 Gone 状态码,指示这个资源已经永久删 除了。如果需要的话,客户端可以在经过用户确认后删除这个资源的链接。如果服务器不能确定客户端所请求的资源是永久的被删除了还是由于某种原因临时不可用 了,那么 404 Not Found 是一个更好的状态码。

                一般情况下,410 Gone 可以用于服务器上一些临时的资源,或者不再提 供的某些资源。例如一些不用存档的通知信息等。

                与 404 Not Found,这个状态码显示更绝情一些,出 现 404 Not Found 时,服务器的意思是过一段时间之后这个资源有可能还会再可用,但是如果收到了 410 Gone,那么很明显的通知了客 户端“你走吧,这个资源已经不在了,而且也找不到它新的地址了”。那么,如果不是那么确定的时候,还是用 404 Not Found 吧,以后还有“挽 回”的可能。

                6.12. 411 Length Required

                客户端发送请求时,如 果是使用的 POST 或 PUT 方法,一般都应该加送一个 Content-Length 头,用来指明所发送请求实体的大小,这样服务器在处理请求 时可以提取出整个请求的数据。

                如果客户端在发送请求时从服务端收到了一个 411 Length Required 状态码,那么应该在原 来请求的基础上添加一个正确可用的 Content-Length 头部字段再重新发送。

                6.13. 412 Precondition Failed

                预 处理失败,这通常在客户端发送来的请求中包含了服务端不允许的头部字段或头部字段中的值或其格式不正确,这样可以防止客户端发送一些非法头部信息。

                我 对这个状态码的定义并不是很懂,如果理解有错希望指正,原文定义在这里

                6.14. 413 Request Entity Too Large

                客 户端在收到 413 Request Entity Too Large 状态码时应该意识到它所发送的请求是否过大了。例如在上传文件时,如果选择了一 个过大的文件,那么服务器在没有接收完所有数据时就可能会发送一个 413 Request Entity Too Large 状态码,这时客户端应该 提示用户文件过大并重新选择一个文件等,同时服务器可能会关闭连接以免继续传输请求浪费带宽。

                如果出现请求实体过大并不是因为服务器不能处 理过大的文件,而是由于一些临时的原因导致了服务器暂时不能客户端的请求,那么服务器可以在发 送 413 Request Entitiy Too Large 状态码后再头部添加一个 Retry-After 字段,表示客户端可以在此时间之后 重新尝试发送请求。

                6.15. 414 Request-URI Too Long

                在 HTTP/1.1 Section 3.2.1推荐的 URI 长度应该控制在 255 字节以内,因为一些比较 老的客户端不支持大于 255 字节的 URI。虽然这个限制在现在已经有些过时了,不过浏览器对 URI 的长度也还是有限制的,例如 在 IE 中 URI 的最大长度在 2083 个字节。测试中发现服务器对于 URI 的长度也是有限制的,这也就是 414 Request- URI Too Long 存在的意义了。在服务器觉得客户端所发送的 URI 过长时,就可以返回一个 414 Request- URI Too Long 状态码向客户端说明发生了什么错误。

                通常这个情况会发生在客户端使用了 GET 方法去发送本应使 用 POST 方法发送的数据,例如将一个表单使用 GET 方法发送,那么表单中的所有字段会以查询字符器的方式附加到 URI 的最后,这时很有可 能 URI 的长度就会超过了服务器的限制,从而使服务器发送了了 414 Request-URI Too Long 状态码。

                还有一种 情况就是用户刻意去构造一个超长的 URI 来攻击服务器了,这个内容不是本篇的范围,就不深入了,我暂时也没能力去讲有关缓冲区溢出之类的内容:)

                6.16. 415 Unsupported Media Type

                客 户端在发送请求时,一般都需要在头部中添加一下 Content-Type 字段,用于指明所发送请求的内容的类型,例如使用 POST 方法发送表单 时 Content-Type 字段的值就为 application/x-www-form-urlencoded。因此,客户端如果收到 了 415 Unsupported Media Type 状态码,也就意味着它发送的请求的格式不被服务器接受,像在 XML-RPC 的调用过程 中,通信体格式应该为 application/xml 或 text/xml,服务端如果严格检查的话,应该在请求格式不为 application /xml 或 text/xml 时给出一个 415 Unsupported Media Type 的状态码,当然这个检查并不是必须的,XML 也 是文本内容,text/plain 的类型应该也是可接受的。

                6.17. 416 Requested Range Not Satisfiable

                在 多线程下载中,下载工具是通过在请求头部中添加 Range 字段来指定客户端所需要的资源的内容范围,服务器通过识别这个 Range 字段,定位到资 源的指定位置,并发送指定范围内的内容。如果客户端请求头部中的 Range 字段的起始值超过了它所请求资源的大小并且请求头部没有 If- Range 字段,那么服务器就会返回 416 Request Range Not Satisfiable,这时客户端最好应该删除已经下载的数据并 重新发送一个不带 Range 字段的请求以确定资源的真实大小。

                如果是使用字节确定范围的话,那么服务器在返 回 416 Request Range Not Satisfiable 状态码的同时,也应该添加一个 Content-Range 字段,用于指定 客户端所请求的资源的实际大小,这样客户端在收到响应时可以根据请求资源的实际大小重新发送一个请求。

                6.18. 417 Expectation Failed

                客 户端发送的请求中如果有 Except 字段,但是服务器无法满足的话,那么就可以返回一个 417 Expectation Failed 状态码。

                这 个状态码一个常见的用途就是客户端的请求带了一个 Except: 100-continue 字段,但是代理服务器或者 Web 服务器不能处理这个处 理 100 Continue,所以服务器返回 417 Expectation Failded 状态码。

                这个状态码 与 100 Continue 以及请求头部 Except 字段之间的关系以及使用可以参考 HTTP/1.1: Connection, Section 8.2.3 Use of the 100 (Continue) Status

                后 记

                上个星期是学期最后一周,一堆考查课作业要交,晕得一塌糊涂……

                未完待续。

                参 考资料

                1. HTTP/1.1 Protocol Section 3.2.1 General Syntax

                2. What is the maximum length of a URL?

                3. What is the limit on QueryString / GET / URL parameters?

                4. HTTP/1.1: Header Field Definitions

                5. HTTP/1.1: Connectinos

                系列目录

                1. Learn HTTP 1.1 Status Code Part.1 : Successful 2xx

                2. Learn HTTP 1.1 Status Code Part.2 : Redirection 3xx

                3. Learn HTTP 1.1 Status Code Part.3 : Client Error 400-405

                Learn HTTP 1.1 Status Code Part. 3

                Learn 1.1 HTTP Status Code Part.3

                by xujiwei(http://www.xujiwei.com/)

                前一篇介绍了 3xx 系列状态码,这一篇介绍 4xx 客户端错误系列。

                6. 客户端错误 4xx 系列状态码

                4xx 系列状态码可以算是对用户来说相当熟悉的一类状态码,这个系列的状态码通常都会带一段描述信息来描述服务端在处理请求时出现了什么状况,用户能直观地看到服务端返回的信息,而不像 3xx 系列,浏览器会自动处理那些个状态码,不需要用户的参与。

                但是 4xx 系列状态码之间的区别只是它所代表的意义不同,它们的表现形式是可以随心所欲的,例如一些著名站点的 404 页面都是精心设计过的。

                6.1. 400 Bad Request

                请求格式错误,这可能存在于客户端构造的 HTTP 头不符合要求等,这时服务端会返回一个 400 Bad Request,而客户端在不修改请求数据的情况,不可以再次发送这个请求。

                6.2. 401 Unauthorized

                未授权错误,用于 HTTP 认证。如果客户端请求的资源需要认证,那么服务端可以响应一个 401 Unauthorized,同时在头部添加一个 WWW-Authenticate 字段表示认证方式,客户端在收到 401 Unauthorized 时,可以提示用户输入用户名和密码,并根据服务端发送过来的认证方式加密认证数据,再重新发送之前的请求。

                HTTP 认证方式中认证数据的传送及格式可以参考 RFC 2617 HTTP Authentication: Basic and Digest Access Authentication

                <%@LANGUAGE=”JScript” CODEPAGE=”65001″%>
                <%
                // code from www.xujiwei.com
                var auth = Request.ServerVariables(“HTTP_AUTHORIZATION”) + “”;
                // 判断客户端输入的用户名及密码是否为 hello 及 world
                // aGVsbG86d29ybGQ= 是将用户名密码以冒号连接并使用 BASE64 编码后的字符串
                if (auth == ‘Basic aGVsbG86d29ybGQ=’) {
                Response.AddHeader(“WWW-Authenticate”, auth);
                Response.Write(“Authorized”);
                }
                else {
                Response.Status = “401 Unauthorized”;
                Response.AddHeader(‘WWW-Authenticate’, ‘Basic realm=AuthTest’);
                Response.Write(“Unauthorized”);
                }
                %>

                6.3. Payment Required

                保留的状态码,看字面意思可以在未来用于电子商务之类的网站。

                6.4. Forbidden

                很直接的,服务端知道客户端想干嘛,但是不想满足它的请求。在服务端XMLHTTP进阶应用-User Agent伪装中有一个简单的例子,使用 ServerXMLHTTP 抓取 Google 新闻的 RSS:

                Learn HTTP 1.1 Status Code Part.2

                Learn HTTP 1.1 Status Code Part.2

                by xujiwei(http://www.xujiwei.com/)

                前一篇介绍了 2xx 系列状态码,这一篇介绍 3xx 重定向系列。

                5 重定向 3xx 系列状态码

                3xx 系列状态码一般是用来作为重定向的,并且在重定向的过程中,一般不需要用户的参与,也就是说,重定向的过程是由浏览器来控制的。但是如果重定向后的请求的方法不是 GET 或 HEAD 的话,还是需要用户参与的,不过也许仅仅是确认一下是否同意发送数据而已:)

                另外,开发者要注意的是,RFC2616 Section 10.3 的备注中提到了在前一版本的 HTTP 协议中建议最大重定向次数为 5 次,也就是说,客户端可能只跟踪 5 次重定向,如果超过 5 次重定向,那么可能客户端就会把第 5 次重定向后得到的结果做为最终结果。这时,客户端得到的可能就是一个错误的结果,这就是开发者不愿看到的,那么在开发时,就应该尽量控制重定向次数在 5 次以内。

                5.1  300 Multiple Choices

                300 Multiple Choices,字面意思是多重选择,表示请求的这个地址具有多个资源可以响应。客户端在遇到 300 Multiple Choices 时,可以自动选择一个最适合的转向地址,或者将所有选项提供给用户由用户来选择一个最适合的转向地址。

                除非客户端使用 HEAD 方法来请求,那么如果服务端要返回 300 状态码,那么也应该同时在响应中指出所有可以选择的转向地址列表以便客户端进行选择。但是,目前这个列表的格式并没有一个统一的规范,所以需要客户端与服务端统一规范。

                我觉得这个状态码可以用于镜像选择,客户端向服务器请求一个资源,服务器返回一个所有候选资源的列表,客户端从中选取一个速度最快的镜像进行资源下载。

                如果没有客户端与服务端之间没有统一规范哪一个资源才是最适合的选择,那么服务端最好在头部中添加 Location 字段指明以服务端来考虑的最适合的转向地址,客户端可以直接使用 Location 字段中的转向地址进行转向而不需要考虑候选资源列表格式是怎么样的或者哪一个选择才是最好的。

                经过测试,目前流行的浏览器中,只有 IE 和 Firefox 会对 300 状态码进行识别,Safari、Opera、Chrome 均直接显示响应的内容。由于前面说过的原因,候选列表的格式没有一个统一的规范,所以 IE 和 Firefox 只会根据 Location 字段的值进行转向,如果不指定 Location 字段的值,那么 IE 和 Firefox 也会跟其他浏览器一样只显示响应的内容。

                可以使用以下 ASP 代码来测试 300 Multiple Choices:

                • <%@LANGUAGE=”JScript” CODEPAGE=”65001″%>
                • <%
                • // code from www.xujiwei.com
                • Response.Status = “300 Multiple Choices”
                • // 添加一个 Location 字段,IE 和 Firefox 会识别这个字段并重定向
                • Response.AddHeader(“Location”, “test3.asp”);
                • // 输出一个候选资源列表,以 XML 文件的形式
                • // 当然,这个格式只是我测试用的,没有浏览器会认识它
                • Response.ContentType = “application/xml”;
                • Response.Write(“<?xml version=’1.0′ encoding=’utf-8′ ?><locations><location>test3.asp</location><location>test.html</location></locations>”);
                • %>

                在 IE 和 Firefox 中浏览包含以上代码的页面时,会自动转向到 test3.asp,而 Safari、Opera、Chrome 则不会。

                5.2  301 Moved Permanently

                在诸多 SEO 的文章中所说的永久转向就是这位 301 Moved Permanently 了。301 表示当前请求的资源永久地被转移到一个新的 URI 了,除非请求的方法是 HEAD,这个新的 URI 必须在头部的 Location 字段中指出,同时响应内容中最好包含一个指定新地址的超链接,这样,在不支持自动转向的浏览器用户也可以通过点击超链接来重定向到新的 URI。

                如果请求时所用的方法不是 GET 或者 HEAD,那么客户端在用户确认前不应自动进行重定向。例如如果浏览器发送了一个 POST 请求,但是收到了 301 Moved Permanently 的响应,那么在重定向到新的 URI 时,可能会使用 GET 方法来请求而不是 POST 方法。

                事实上,测试表明,目前的主流浏览器都不会直接重发 POST 请求到 301 重定向后的 URI,而是将请求转换成 GET 方法后再发送,也就是说,在 301 重定向到的服务端程序中,是接收不到最开始客户端 POST 的数据。

                通过以下代码可以测试出 POST 请求被转发为 GET 请求:

                test.html

                • <!– code from www.xujiwei.com –>
                • <form action=”test.asp” method=”post”>
                • <input type=”text” name=”hello” value=”world” /><input type=”submit” />
                • </form>

                test.asp

                • <%@LANGUAGE=”JScript” CODEPAGE=”65001″%>
                • <%
                • // code from www.xujiwei.com
                • // 重定向到 test2.asp
                • Response.Status = “301 Moved Permanently”
                • Response.AddHeader(“Location”, “test2.asp”);
                • %>

                test2.asp

                • <%@LANGUAGE=”JScript” CODEPAGE=”65001″%>
                • <%
                • // code from www.xujiwei.com
                • Response.ContentType = “text/html; charset=utf-8”;
                • Response.Write(new Date().toLocaleString() + “<br/>”);
                • // 输出请求方法,预期为 POST,实际为 GET
                • Response.Write(Request.ServerVariables(“HTTP_METHOD”));
                • // 输出表单中字段 hello 的值,预期为 “world”,实际为空字符串
                • Response.Write(Request.Form(“hello”));
                • %>

                5.3  302 Found

                302 Found 常用于客户端重定向,例如登录完成后重定向到首页等。它表示的是临时重定向,虽然用得多,但是在对于搜索引擎而言却是一个不太友好的东西,所以尽量只在临时转向时才用它。

                302 Found 的用法跟 301 Moved Permanently 一样,在 Location 字段中指出要转向的地址,只是所包含的含义不一样而已。

                另外,在 RFC 2616 Section 10.3.3 的备注中说到在 RFC 1945 和 RFC 2068 中是不允许客户端改变重定向后的请求方法的,但是目前多数的客户端都是直接重定向然后把方法改为 GET 并舍去 POST 部分的数据,所以在最新的 HTTP 1.1 中添加了 303 和 307 两个状态码来明确是否需要客户端继续以 POST 方式发送请求。

                5.4  303 See Other

                上面提到了 303 和 307 是用来解决重定向是否要重发 POST 数据不清晰的问题,其中 303 就是用来表示客户端不需要重新发送 POST 数据,可以使用 GET 方法来请求新的地址。

                要注意的是,303 响应中 Location 指出的地址并不是原请求地址的完整响应,也就是说,303 可能只是个说明页面,并不一定会对原来请求做出正确的响应

                备注中说一些以前的 HTTP 1.1 客户端可能不认识 303,不过现在可以放心的是,主流浏览器都是支持 303 状态码的。

                test.asp

                • <%@LANGUAGE=”JScript” CODEPAGE=”65001″%>
                • <%
                • // code from www.xujiwei.com
                • Response.Status = “303 See Other”
                • Response.AddHeader(“Location”, “test2.asp”);
                • %>

                反正客户端是会将请求以 GET 方法重新发送的,所以 POST 部分的数据就不用指望了:)

                5.5  304 Not Modified

                304 Not Modified 是用来表示客户端所请求的资源和上次所请求时没有发生改变,这样服务端就不用重新发送资源的内容,从而减少了网络的负担。

                服务端可以由两个头部变量来判断是否发送 304 Not Modified 状态码,一个是 If-Modified-Since,另一个是 If-None-Match,相对应的服务端需要发送的头部变量是 Last-Modified 和 ETag。可以二者选其一,也可以两个都用。PJBlog 就使用了 304 Not Modified 来优化边栏的性能。

                • <%@LANGUAGE=”JScript” CODEPAGE=”65001″%>
                • <%
                • // code from www.xujiwei.com
                • // 从 Session 中获取 ETag 和 Last-Modified 值并发送到客户端
                • var etag = Session(‘etag’) + ‘, lastmod = Session(“lastmod”) + ‘;
                • if(etag == ‘ || etag == ‘undefined’) {
                • Session(‘etag’) = (+new Date()).toString(16);
                • etag = Session(‘etag’) + ‘;
                • }
                • if(lastmod == ‘ || lastmod == ‘undefined’) {
                • Session(‘lastmod’) = new Date().toUTCString();
                • lastmod = Session(‘lastmod’) + ‘;
                • }
                • Response.AddHeader(“ETag”, etag);
                • Response.AddHeader(“Last-Modified”, lastmod);
                • Response.ContentType = “image/jpeg”;
                • // 获取客户端发送过来的 If-None-Match 和 If-Modified-Since 值
                • var etag2 = Request.ServerVariables(“HTTP_IF_NONE_MATCH”) + “”;
                • var lastmod2 = Request.ServerVariables(“HTTP_IF_MODIFIED_SINCE”) + “”;
                • if(etag2 == etag || lastmod == lastmod2) {
                • // 直接响应 304 Not Modified
                • Response.Status = “304 Not Modified”;
                • }
                • else {
                • // 发送完成内容
                • Response.Status = “200 OK”
                • var stream = new ActiveXObject(“ADODB.Stream”);
                • stream.Type = 1;
                • stream.Mode = 3;
                • stream.Open();
                • stream.LoadFromFile(Server.MapPath(‘test.jpg’));
                • Response.BinaryWrite(stream.Read());
                • stream.Close();
                • }
                • %>

                5.6  305 Use Proxy

                305 Use Proxy 指示客户端需要通过代理来请求当前资源,在头部的 Location 字段中指定代理地址,并且这个状态码只能由原始服务器返回。

                但是 RFC 2616 Section 10.3.6 中并没有详细指出 Location 的格式是怎么样的,通过测试发现只有 Safari 会识别 305 Use Proxy,并且直接转向到 Location 中指定的地址。

                暂且把它当做一个转向的状态码来用吧,嗯,尽量少用吧,许多浏览器不认识它。

                5.7  306 (Unused)

                HTTP 1.1 中把它给扔了,所以我们不用管这个状态码了,如果碰到了,直接当错误处理吧。

                5.8  307 Temporary Redirect

                唔,又是一个不被完全支持的状态码。客户端收到这个状态码时,应该将请求重发到新的地址。而且这个状态码,主要用于 POST 请求,因为如果原始请求是用 GET 方法发送的,那么很可怜的在重定向到新的地址以后,GET 参数会全部舍弃,如果需要在转向后的地址处理 GET 参数,那么自己在服务端程序中加上去吧……

                测试中只有 Firefox、Opera 和 IE 会将 POST 请求重定向到新的地址,其中 Firefox 和 Opera 会让用户选择是否重新发送表单到新的地址,而 IE 则是不经用户选择直接把表单重新发送到新的地址,相对而言,Firefox 和 Opera 更安全一些。

                如果一定要用这个状态码,为了兼容那些个不认识它的浏览器,服务端需要在响应内容中加个指向新地址的超链接,再加个说明啥的,说明一下怎么用这个新的地址。anyway,少用为上。

                后记

                3xx 重定向系列用得相当广泛,301、302、304 这三个用得最多。特别需要注意的是 POST 的数据在目前来说基本上是无法直接重定向到一个新的地址的。

                ps. 用好 304 很重要:)

                系列目录

                Learn HTTP 1.1 Status Code Part.1

                Comments (0), Views (1091), Pings (0), Leave a response!

                Learn HTTP 1.1 Status Code Part.1

                Learn HTTP Status Code Part.1

                by xujiwei(http://www.xujiwei.com/)

                1. 前言

                HTTP 状态码在 Web 开发中是相当重要的一部分,掌握了 HTTP 的运用,可以在网站速度、性能等等的优化上有迹可循。当前,掌握 HTTP 状态码也可以为进一步加深对 HTTP 协议的理解打下基础。

                以前,我所熟知的 HTTP 状态码仅限于 200、404、302 等几个,但是在一次与 smallshell 聊天的过程中,谈到了 HTTP 状态码,让我对剩余许多我并不熟知的 HTTP 状态码起了兴趣。不过在这之后的一段时间里都比较忙,因此这个想写的这个系列就一直拖着,这几天算是稍微清闲下来了,准备好好学习了一下 HTTP 状态码。

                嗯,我也是在学习的过程中,所以,如果我写的有什么不对,欢迎指正,留言或 email(vipxjw at 163 dot com) 均可:)

                2. 准备

                本文基于 RFC2616 第10节 Status Code Definitions 而写,如果对本文有不明白的地方,可以参考完整的 HTTP 1.1 协议

                3. 1xx 系列

                1xx 系列一般是用在服务器与代理之间,表示一些信息,这在我们写服务端程序时并不常用,并且 1xx 系列状态码是在 HTTP 1.1 中才加入的,按照 w3c 的说法,除非在实验性的环境下,不要发送 1xx 系列状态码到使用 HTTP 1.0 协议的客户端

                如果需要了解两个 1xx 系列的状态 100 Continue 和 101 Switching Protocols 的作用,可以在 RFC2616 第10节中找到相关资料。

                4. 2xx 系列

                2xx 系列表示成功的响应,最为人所知的就是 200 OK 了,它表示一下正确响应了客户端所发送过来的请求,那么在客户端,也就需要以“正确”的态度来对待服务端过来的响应内容。

                4.1 200 OK

                呃,200 OK 是最最常见的状态码了,每次我们在浏览器输入一个网址按下回车后,就会收到一堆 200 OK,就是一堆 200 OK,呈现给我们了一个丰富多彩的页面。

                因此,通常情况下,在编写服务端程序时,也应该返回一个 200 OK 的状态码。但是也因为 200 OK 是如此的普通,如此的常用,可能在一些特殊的情况下,开发人员没有注意到 200 OK 并不是一个可取的状态码,而这个特殊的情况,最明显的一个就是采用了 自定义错误页 或 Url Rewrite 的时候。

                在使用了自定义错误页或者 Url Rewrite 时,如果碰到了一个并不存在的资源,服务器会将请求重定向到指定的页面,而这时服务器会认为请求得到了正确的响应,响应状态码会被设置为 200 OK,如果在程序中不另加设置,客户端就会请求一个不存在的资源时得到了 200 OK 这个本该只在得到正确响应时才能得到的状态码。这对于一般用户来说,可能没有多大的影响,但对于搜索引擎却是不友好的,搜索引擎会不断的索引这些并不存在的资源。

                所以,如果使用了自定义错误页或 Url Rewrite,那么需要在程序中根据实际情况来设置状态码是 200 OK 还是 404 Not Found

                例如,在 IIS 中设置自定义错误页为 404.asp 后,IIS 会把对不存在资源的请求重定向到 404.asp,状态码被设置为 200 OK,在 404.asp 中,就需要把状态码给改回来:

                • <%
                • Response.Status = “404 Not Found”
                • %>

                4.2 201 Created

                201 Created 通常用来表示请求的资源不存在,但是已经被创建了。一般可以用于一些发布协议,如 Atom Publishing Protocol 等,你在使用 Atom 协议去发布一篇 blog 时,服务端可能会返回一个 201 Created 的状态码,表示你发送的请求已经被接受了,并且你的文章也已经成功创建了。

                如果服务端要返回一个 201 Created 状态码,那么同时也应该在响应体头部的 Location 字段中指出新创建资源的位置,例如如果是发布一篇 blog,那么服务端在返回 201 状态码的时候,同时在 Location 字段中指出这篇新的 blog 的 URI 是什么,这样客户端可以根据这个 URI 立即去访问这篇 blog。

                4.3 202 Accepted

                在 201 Created 中提到,使用发布协议时,服务端可以返回一个 201 的状态码,提示客户所发送的请求已经被正确响应,并且资源也已经被正确创建了。但是在某些时候,客户所请求的资源并不是可以立即创建,可能需要被审核,可能在一个队列当中等等,在这时,服务端并不能直接返回一个 201 Created 状态码,因为返回这个状态码时,客户端认为它所发送的请求已经被正确处理了,但事实中,这个请求仍旧在处理过程之中。

                这个时候,202 Accepted 状态码就是一个更好的选择。202 Accepted 可以理解为:你的请求我收到了,但是还没有处理完成,请稍候。更实际一点,你可以理解为你去某个 Google Group 申请了成员资格,Google Group 系统接受了你的申请,但是这个 Group 是需要管理员审核的,于是,你得到了一个 202 Accepted 的状态,表示你的请求已经被收到了,但是还没有处理完成,你需要等待。

                202 Accepted 要求客户端等待服务端的处理完成,但是 HTTP 协议是无状态的,因此服务端并不会也不能主动去通知客户商,说“哎,你的请求已经处理好了,地址是xxx”。那么,跟踪请求状态这个任务就需要客户端去做。但是客户端要跟踪,也得有个对象,那个这个对象就是服务端该提供给客户端的了。

                服务端在返回 202 Accepted 状态码的同时,应该给出当前请求的状态,或者给出一个状态监视器的地址。

                服务端直接在响应 202 Accepted 状态码的同时给出请求处理的状态就相当于我们打电话去订餐,第一次打电话给餐厅,餐厅响应一个 202 Accepted:“啊,我们收到你的订单了,一会儿就给您送去。”,那么等了一会,怎么还没来,再打电话过去,餐厅接着响应一个 202,并告诉我们:“您的订单已经在路上了,过会就能收到了,请稍等。”,于是,我们可以通过不断地发送请求来知道我们的订单到底怎么样了。

                而另外一种方式,服务端返回一个状态监视器的地址则相当在我们在申请某个 Google Group 成员资格时,提交申请之后,我们要做的就是看看邮箱,看看有没有来自 Google Group 的审核邮件。

                当然,客户端与服务端对 202 Accepted 所返回的响应体应该有一个共同的规范,这样才能通过这个响应来了解请求的处理进程。

                4.4 203 Non-Authoritative Information

                203 表示非权威应答,说明这个响应头可能并不是原始服务器的响应头,在中间传输过程中附加了一些其他的信息,诸如通过了一些代理,加了一些额外的头部信息等等。

                4.5 204 No Content

                服务端返回 204 No Content 这个状态码时,表示客户端发送的已经正确响应了,但是并不会返回响应实体,只会更新头部信息。客户端在收到 204 No Content 时,当前显示的内容不会变化,显示的内容还是之前的文档,只是地址栏里的地址已经变成新的网址。

                4.6 205 Reset Content

                按照 RFC2616 中的描述,客户端在收到 205 Reset Content 状态码时,应该重置当前文档内容,相当于重新加载发送请求的页面,但是测试发现,Firefox 3.0 与 Chrome 依旧显示当前文档,不会重新加载,而 Safari 和 IE 则直接重定向到了所请求的网址,相当于不支持 205 Reset Content 状态码。

                4.7 206 Partial Content

                206 Partial Cotent 在多线程下载中相当常见,它表示服务端只处理了部分内容或者是只返回了部分内容。

                按照 RFC2616 说明,在返回 206 Partial Content 状态码时,服务端需要在头部中指定一些必需的字段,如 Date、Content-Length 等,这些头部字段的使用都有一定的条件及限制,如 Content-Length 必须与响应体的实际大小一致,如果在响应 200 OK 时服务端发送了 ETag 或者 Content-Location 的头部字段,在响应 206 Partial Content 时也必须发送同样的头部字段,具体可以参考 RFC2616 Section 10.2.7

                例如可以在 ASP 中实现部分响应来支持多线程下载:

                • <%@LANGUAGE=”JScript” CODEPAGE=”65001″%>
                • <%
                • // code from www.xujiwei.com
                • // 获取请求中是否有 Range 参数
                • var http_range = Request.ServerVariables(“HTTP_RANGE”) + ‘;
                • var filename = “asplite.zip”;
                • // 创建 ADODB.Stream 对象并载入文件
                • var stream = new ActiveXObject(“ADODB.Stream”);
                • stream.Type = 1;
                • stream.Mode = 3;
                • stream.Open();
                • stream.LoadFromFile(Server.MapPath(filename));
                • // 让客户端自动识别文件类型及文件名
                • Response.ContentType = “application/x-zip-compressed”;
                • Response.AddHeader(“Content-Disposition”, “inline; filename=” + filename);
                • // 判断是否响应部分
                • if(http_range == ‘ || http_range == ‘undefined’) {
                • Response.Status = “200 OK”;
                • Response.BinaryWrite(stream.Read());
                • }
                • else {
                • Response.Status = “206 Partial Content”;
                • var ranges = http_range.substr(6).split(“-“);
                • var start = +ranges[0], end = +ranges[2];
                • stream.Position = start;
                • var size = end – start;
                • if(size > 0) {
                • Response.BinaryWrite(stream.Read(size));
                • }
                • else {
                • Response.BinaryWrite(stream.Read());
                • }
                • }
                • %>

                当然,上面代码中的判断部分并不严密,但是,多线程下载工具也并不一定严格遵守这些规则,所以,还是可以偷偷懒的。

                后记

                断网了几天,断电了两天,这篇文章终于还是出来了。1xx 和 2xx 系列的写完了,接下来就是重定向的 3xx 系列了。

                参考资料

                1. HTTP Status: 201 Created vs. 202 Accepted

                2. Google 帮助:HTTP 状态代码

                3. 兔子洞:HTTP响应状态码

                服务端XMLHTTP(ServerXMLHTTP in ASP)进阶应用-User Agent伪装

                服务端XMLHTTP(ServerXMLHTTP in ASP)进阶应用-User Agent伪装

                这篇开始讲讲ServerXMLHTTP的进阶应用。说是进阶应用,但也就是讲一些在基本应用里没有讲到的属性或者方法之类:)

                使用setRequestHeader伪装User-Agent

                User-Agent一般是服务端程序用来判断客户端浏览器、操作系统等信息的标志,它的说明可以参考Wiki,譬如在我的电脑 IE7 的UA就是:

                Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1) ;

                SLCC1; .NET CLR 2.0.50727; .NET CLR 3.5.21022; .NET CLR 3.5.30729; .NET CLR 3.0.30618)

                可以看到,这个UA提供了不少信息,IE版本、Windows版本、.NET版本都有。再看看 Firefox 的:

                Mozilla/5.0 (Windows; U; Windows NT 6.0; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3 (.NET CLR 3.5.30729)

                UA的格式不是本文的重点,因此,如果需要了解UA的具体格式,可以去Google上找找,另外,在http://www.user-agents.org/这里可以找到目前大多数浏览器、搜索引擎Spider等的UA。

                在我们使用 ServerXMLHTTP 发送请求时,它所用的 User-Agent 是以下内容:

                Mozilla/4.0 (compatible; Win32; WinHttp.WinHttpRequest.5)

                但是,某些网站会限制这个UA的访问,比如Google,如果我们使用以下代码来请求Google的页面,它使用的是默认UA“Mozilla/4.0 (compatible; Win32; WinHttp.WinHttpRequest.5)”:

                <%@LANGUAGE=”JScript” CODEPAGE=”65001″%>
                <%
                // code from xujiwei
                // http://www.xujiwei.cn
                var url = “http://news.google.cn/?output=rss”;
                var xmlhttp = new ActiveXObject(“MSXML2.ServerXMLHTTP.5.0”);
                xmlhttp.open(“GET”, url, false);
                //xmlhttp.setRequestHeader(“User-Agent”, “Mozilla/5.0 (Windows; U; Windows NT 6.0; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3 (.NET CLR 3.5.30729)”);
                xmlhttp.send(“”);
                Response.BinaryWrite(xmlhttp.responseBody);
                xmlhttp = null;
                %>

                你会发现,我们会收到一个 403 Forbidden 的错误:

                为了使得Google的RSS输出程序能把我们的识别成正常的RSS阅读或者一般浏览器,就需要在请求发出前设置 Request Header。要设置 Request Header,只需要简单的在调用 open 方法之后,send 方法之前使用 setRequestHeader 来设置就行了,它的语法是 xmlhttp.setRequestHeader(key, value)。下面我们就让Google的RSS输出程序把我们的请求识别为Firefox的请求:

                <%@LANGUAGE=”JScript” CODEPAGE=”65001″%>
                <%
                // code from xujiwei
                // http://www.xujiwei.cn
                var url = “http://news.google.cn/?output=rss”;
                var xmlhttp = new ActiveXObject(“MSXML2.ServerXMLHTTP.5.0”);
                xmlhttp.open(“GET”, url, false);
                // 设置 User Agent 为 Firefox 的UA
                xmlhttp.setRequestHeader(“User-Agent”, “Mozilla/5.0 (Windows; U; Windows NT 6.0; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3 (.NET CLR 3.5.30729)”);
                xmlhttp.send(“”);
                Response.BinaryWrite(xmlhttp.responseBody);
                xmlhttp = null;
                %>

                刷新浏览器,显示出了Firefox的RSS预览界面,获取Google资讯RSS成功!

                ok,我们能正确得到Google资讯的RSS了,再通过XMLDOM来操作返回的RSS文档,就可以采集Google资讯那海量的新闻了。

                to be continued.

                系列目录:

                1. 服务器XMLHTTP(Server XMLHTTP in ASP)基础

                2. 服务端XMLHTTP(ServerXMLHTTP in ASP)基本应用(上)

                3. 服务端XMLHTTP(ServerXMLHTTP in ASP)基本应用(下)

                by xujiwei

                http://www.xujiwei.com/