Request 接收参数乱码原理解析一:服务器端解码原理

8/10/2015来源:ASP.NET技巧人气:2353

Request 接收参数乱码原理解析一:服务器端解码原理

“Server.UrlDecode(Server.UrlEncode("北京")) == “北京””,先用UrlEncode编码然后用UrlDecode解码,这条语句永远为true吗?答案是否定的,结果可能与很多人预想的不大一样。本文主要分析这一问题出现的原理,研究下Server.UrlEncode(),Server.UrlDecode(),Request["xxx"]三个函数与编码方式的关系。

1. 问题出现的情景

网站采用了GB2312编码,在Web.config中添加如下配置。

  <system.web>    <globalization requestEncoding="GB2312" responseEncoding="GB2312"/>  </system.web>

测试页面EncodeServerTest.aspx.cs代码。

        PRotected void Page_Load(object sender, EventArgs e)        {            string s = Server.UrlDecode(Server.UrlEncode("北京"));            bool isEqual = s == "北京";        }

测试页面EncodeServerTest.aspx代码。

<html xmlns="http://www.w3.org/1999/xhtml"><head runat="server">    <title>页面编码测试(服务器端)</title>    <script type="text/javascript" src="Scripts/jquery-2.1.1.min.js"></script></head><body>    <form id="form1" runat="server">        <div>            <input type="button" name="btnAjaxPost" value="AJax提交" onclick="Ajax()" />            <div id="divMessage" style="color: red"></div>        </div>    </form>    <script type="text/Javascript">        function Ajax() {            $.ajax({                type: "POST",                url: "EncodeServerTest.aspx",                data: {name:"name"},                success: function (data) {                    $("#divMessage").html(data);                }            });        }    </script></body></html>
View Code

运行页面,首次执行时,编码解码方式都为GB2312,isEuqal=true;点击页面的button,通过ajax再次请求页面,编码方式仍为GB2312,但解码方式变成了UTF-8,于是s值成了乱码,isEqual=false。下面两个图分别为两次执行的结果:

实际项目遇到问题的场景比这复杂,但也是因为UrlEncode编码和UrlDecode解码方式不一致造成的,本系列的第三篇会有实际项目场景的说明。要解释这一现象,必须了解UrlEncode()和UrlDecode()的实现。

2. Server.UrlEncode()函数

反编译UrlEncode()函数,实现如下:

        public string UrlEncode(string s)        {            Encoding e = (this._context != null) ? this._context.Response.ContentEncoding : Encoding.UTF8;            return HttpUtility.UrlEncode(s, e);        }

源码可以看出,有上下文时用的是Response.ContentEncoding,没有上下文时默认用UTF-8编码。关键是Response.ContentEncoding的实现,继续反编译ContentEncoding的实现:

        public Encoding ContentEncoding        {            get            {                if (this._encoding == null)                {                    GlobalizationSection globalization = RuntimeConfig.GetLKGConfig(this._context).Globalization;                    if (globalization != null)                    {                        this._encoding = globalization.ResponseEncoding;                    }                    if (this._encoding == null)                    {                        this._encoding = Encoding.Default;                    }                }                return this._encoding;            }        }

结论:UrlEncode()函数,优先从取配置文件的Globalization结点获取,如果配置文件没有的话用Encoding.Default,最后默认用Encoding.UTF8。

3. Server.UrlDecode()函数

反编译UrlEncode()函数,实现如下:

        public string UrlDecode(string s)        {            Encoding e = (this._context != null) ? this._context.Request.ContentEncoding : Encoding.UTF8;            return HttpUtility.UrlDecode(s, e);        }

从源码可以看出,有上下文时用的是Request.ContentEncoding,没有上下文时默认用UTF-8编码。关键是Request.ContentEncoding的实现,继续反编译ContentEncoding的实现:

        public Encoding ContentEncoding        {            get            {                if (!this._flags[0x20] || (this._encoding == null))                {                    this._encoding = this.GetEncodingFromHeaders();                    if ((this._encoding is UTF7Encoding) && !AppSettings.AllowUtf7RequestContentEncoding)                    {                        this._encoding = null;                    }                    if (this._encoding == null)                    {                        GlobalizationSection globalization = RuntimeConfig.GetLKGConfig(this._context).Globalization;                        this._encoding = globalization.RequestEncoding;                    }                    this._flags.Set(0x20);                }                return this._encoding;            }            set            {                this._encoding = value;                this._flags.Set(0x20);            }        }

从源码可以看出,Request.ContentEncoding先通过函数GetEncodingFromHeaders()获取,如果获取不到,则从配置文件获取,接下来看GetEncodingFromHeaders()的实现:

        private Encoding GetEncodingFromHeaders()        {            if ((this.UserAgent != null) && CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this.UserAgent, "UP"))            {                string str = this.Headers["x-up-devcap-post-charset"];                if (!string.IsNullOrEmpty(str))                {                    try                    {                        return Encoding.GetEncoding(str);                    }                    catch                    {                    }                }            }            if (!this._wr.HasEntityBody())            {                return null;            }            string contentType = this.ContentType;            if (contentType == null)            {                return null;            }            string attributeFromHeader = GetAttributeFromHeader(contentType, "charset");            if (attributeFromHeader == null)            {                return null;            }            Encoding encoding = null;            try            {                encoding = Encoding.GetEncoding(attributeFromHeader);            }            catch            {            }            return encoding;        }
View Code

从GetEncodingFromHeaders()的源码可以看出,先从HTTP请求头(x-up-devcap-post-charset或者charset)获取编码信息,如果编码合法的话则采用HTTP请求头指定的编码方式解码。

结论:UrlDecode()函数,优先从HTTP请求头(x-up-devcap-post-charset或者charset)获取编码,如果没指定的话从取配置文件的Globalization结点获取,最后默认Encoding.UTF8。

通过对UrlEncode()和UrlDecode()源码的分析,可以看出两者在确定编码上并不一致,UrlDecode()和HTTP请求的头有关,而通过Fiddler对比EncodeServerTest.aspx页面的两次请求,发现通过Ajax方式的请求,请求头正好多了“Content-Type:application/x-www-form-urlencoded; charset=UTF-8”一句,文章开始的问题得以解释。

补充:获取Response.ContentEncoding和Request.ContentEncoding时,还有一个重要的函数”GlobalizationSection globalization = RuntimeConfig.GetLKGConfig(this._context).Globalization“,网上关于这个函数的资料很少,反编译后代码也很复杂,看的云里雾里,下面摘录一部分代码,从总可以猜测这个函数的功能:根据配置文件的继承关系,取配置文件中Globalization结点的Request和Response编码方式,如果没取到的话默认取UTF-8编码,个人感觉获取Request.ContentEncoding时的分支Encoding.Default赋值应该不会被执行。

        internal static RuntimeConfig GetLKGConfig(HttpContext context)        {            RuntimeConfig lKGRuntimeConfig = null;            bool flag = false;            try            {