“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>

运行页面,首次执行时,编码解码方式都为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;
}

从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
{
lKGRuntimeConfig = GetConfig(context);
flag = true;
}
catch
{
}
if (!flag)
{
lKGRuntimeConfig = GetLKGRuntimeConfig(context.Request.FilePathObject);
}
return lKGRuntimeConfig.RuntimeConfigLKG;
} //先取网站的配置文件,然后取本机的配置文件
private static RuntimeConfig GetLKGRuntimeConfig(VirtualPath path)
{
try
{
path = path.Parent;
}
catch
{
path = System.Web.Hosting.HostingEnvironment.ApplicationVirtualPathObject;
}
while (path != null)
{
try
{
return GetConfig(path);
}
catch
{
path = path.Parent;
}
}
try
{
return GetRootWebConfig();
}
catch
{
}
try
{
return GetMachineConfig();
}
catch
{
}
return GetNullRuntimeConfig();
} //配置文件有的话,返回配置文件的编码方式;配置文件没有的话返回UTF-8编码方式
//感觉获取Request.ContentEncoding时的Encoding.Default应该不会被执行
[ConfigurationProperty("responseEncoding", DefaultValue = "utf-8")]
public Encoding ResponseEncoding
{
get
{
if (this.responseEncodingCache == null)
{
this.responseEncodingCache = Encoding.UTF8;
}
return this.responseEncodingCache;
}
set
{
if (value != null)
{
base[_propResponseEncoding] = value.WebName;
this.responseEncodingCache = value;
}
else
{
base[_propResponseEncoding] = value;
this.responseEncodingCache = Encoding.UTF8;
}
}
} //配置文件有的话,返回配置文件的编码方式;配置文件没有的话返回UTF-8编码方式
[ConfigurationProperty("requestEncoding", DefaultValue = "utf-8")]
public Encoding RequestEncoding
{
get
{
if (this.requestEncodingCache == null)
{
this.requestEncodingCache = Encoding.UTF8;
}
return this.requestEncodingCache;
}
set
{
if (value != null)
{
base[_propRequestEncoding] = value.WebName;
this.requestEncodingCache = value;
}
else
{
base[_propRequestEncoding] = value;
this.requestEncodingCache = Encoding.UTF8;
}
}
}

4. Request["xxx"]

Request[key],根据指定的key,依次访问QueryString,Form,Cookies,ServerVariables这4个集合,如果在任意一个集合中找到了,就立即返回。通常如果请求是用GET方法发出的,那我们一般是访问QueryString去获取用户的数据,如果请求是用POST方法提交的, 我们一般使用Form去访问用户提交的表单数据。

        public string this[string key]
{
get
{
string str = this.QueryString[key];
if (str != null)
{
return str;
}
str = this.Form[key];
if (str != null)
{
return str;
}
HttpCookie cookie = this.Cookies[key];
if (cookie != null)
{
return cookie.Value;
}
str = this.ServerVariables[key];
if (str != null)
{
return str;
}
return null;
}
}

Request.QueryString[key]实现源码如下,从中可以看到经过层层调用,最终调用的是”base.Add(HttpUtility.UrlDecode(str, encoding), HttpUtility.UrlDecode(str2, encoding));“添加到集合中,而是用的解码方式encoding和Server.UrlDecode()函数是一致的,都是Request.ContentEncoding。

        //QueryString[key]实现
public NameValueCollection QueryString
{
get
{
this.EnsureQueryString();
if (this._flags[])
{
this._flags.Clear();
this.ValidateHttpValueCollection(this._queryString, RequestValidationSource.QueryString);
}
return this._queryString;
}
}
//QueryString[key]调用EnsureQueryString()初始化数据
internal HttpValueCollection EnsureQueryString()
{
if (this._queryString == null)
{
this._queryString = new HttpValueCollection();
if (this._wr != null)
{
this.FillInQueryStringCollection();
}
this._queryString.MakeReadOnly();
}
return this._queryString;
} //FillInQueryStringCollection()函数解码,用的解码方式为QueryStringEncoding
private void FillInQueryStringCollection()
{
byte[] queryStringBytes = this.QueryStringBytes;
if (queryStringBytes != null)
{
if (queryStringBytes.Length != )
{
this._queryString.FillFromEncodedBytes(queryStringBytes, this.QueryStringEncoding);
}
}
else if (!string.IsNullOrEmpty(this.QueryStringText))
{
this._queryString.FillFromString(this.QueryStringText, true, this.QueryStringEncoding);
}
} //解码函数
internal void FillFromString(string s, bool urlencoded, Encoding encoding)
{
int num = (s != null) ? s.Length : ;
for (int i = ; i < num; i++)
{
this.ThrowIfMaxHttpCollectionKeysExceeded();
int startIndex = i;
int num4 = -;
while (i < num)
{
char ch = s[i];
if (ch == '=')
{
if (num4 < )
{
num4 = i;
}
}
else if (ch == '&')
{
break;
}
i++;
}
string str = null;
string str2 = null;
if (num4 >= )
{
str = s.Substring(startIndex, num4 - startIndex);
str2 = s.Substring(num4 + , (i - num4) - );
}
else
{
str2 = s.Substring(startIndex, i - startIndex);
}
if (urlencoded)
{
base.Add(HttpUtility.UrlDecode(str, encoding), HttpUtility.UrlDecode(str2, encoding));
}
else
{
base.Add(str, str2);
}
if ((i == (num - )) && (s[i] == '&'))
{
base.Add(null, string.Empty);
}
}
} //QueryString[key]调用的解码方式为ContentEncoding,和Server.UrlDecode()一致
internal Encoding QueryStringEncoding
{
get
{
Encoding contentEncoding = this.ContentEncoding;
if (!contentEncoding.Equals(Encoding.Unicode))
{
return contentEncoding;
}
return Encoding.UTF8;
}
}

Request.Form[key]实现源码如下,从中可以看到经过层层调用,最终调用的是”HttpUtility.UrlDecode(bytes, num4 + 1, (i - num4) - 1, encoding);“添加到集合中,而调用的解码方式encoding和Server.UrlDecode()函数是一致的,都是Request.ContentEncoding。

        //Form[key]实现
public NameValueCollection Form
{
get
{
this.EnsureForm();
if (this._flags[])
{
this._flags.Clear();
this.ValidateHttpValueCollection(this._form, RequestValidationSource.Form);
}
return this._form;
}
}
internal HttpValueCollection EnsureForm()
{
if (this._form == null)
{
this._form = new HttpValueCollection();
if (this._wr != null)
{
this.FillInFormCollection();
}
this._form.MakeReadOnly();
}
return this._form;
} private void FillInFormCollection()
{
if ((this._wr != null) && this._wr.HasEntityBody())
{
string contentType = this.ContentType;
if ((contentType != null) && (this._readEntityBodyMode != System.Web.ReadEntityBodyMode.Bufferless))
{
if (StringUtil.StringStartsWithIgnoreCase(contentType, "application/x-www-form-urlencoded"))
{
byte[] bytes = null;
HttpRawUploadedContent entireRawContent = this.GetEntireRawContent();
if (entireRawContent != null)
{
bytes = entireRawContent.GetAsByteArray();
}
if (bytes == null)
{
return;
}
try
{
this._form.FillFromEncodedBytes(bytes, this.ContentEncoding);
return;
}
catch (Exception exception)
{
throw new HttpException(System.Web.SR.GetString("Invalid_urlencoded_form_data"), exception);
}
}
if (StringUtil.StringStartsWithIgnoreCase(contentType, "multipart/form-data"))
{
MultipartContentElement[] multipartContent = this.GetMultipartContent();
if (multipartContent != null)
{
for (int i = ; i < multipartContent.Length; i++)
{
if (multipartContent[i].IsFormItem)
{
this._form.ThrowIfMaxHttpCollectionKeysExceeded();
this._form.Add(multipartContent[i].Name, multipartContent[i].GetAsString(this.ContentEncoding));
}
}
}
}
}
}
} internal void FillFromEncodedBytes(byte[] bytes, Encoding encoding)
{
int num = (bytes != null) ? bytes.Length : ;
for (int i = ; i < num; i++)
{
string str;
string str2;
this.ThrowIfMaxHttpCollectionKeysExceeded();
int offset = i;
int num4 = -;
while (i < num)
{
byte num5 = bytes[i];
if (num5 == 0x3d)
{
if (num4 < )
{
num4 = i;
}
}
else if (num5 == 0x26)
{
break;
}
i++;
}
if (num4 >= )
{
str = HttpUtility.UrlDecode(bytes, offset, num4 - offset, encoding);
str2 = HttpUtility.UrlDecode(bytes, num4 + , (i - num4) - , encoding);
}
else
{
str = null;
str2 = HttpUtility.UrlDecode(bytes, offset, i - offset, encoding);
}
base.Add(str, str2);
if ((i == (num - )) && (bytes[i] == 0x26))
{
base.Add(null, string.Empty);
}
}
}

Request.Cookies[key],最终没有调用解码函数,只是把HTTP请求中Cookie值取出来了,如果存储Cookie时,对数据进行了编码处理,通过Request.Cookies[key]获取到Cookie值,需要调用对应的解码函数进行解码。最好调用函数HttpUtility.UrlDecode(str, encoding)解码,以免因为HTTP请求不同造成解码方式不同而出错(对应Server.UrlDecode()函数)。

5. 本文结论

Request.QueryString[key]、Request.Form[key]默认都会调用函数HttpUtility.UrlDecode(str, encoding),如果HTTP请求的数据只经过一次编码,无需再调用解码函数;Request.Cookies[key]没用调用解码函数,获取到值后需要调用正确的解码函数才能得到正确的值。

Request.QueryString[key]、Request.Form[key]、Server.UrlDecode(),解码方式获取是一致的,都是优先从HTTP请求头(x-up-devcap-post-charset或者charset)获取编码,如果没指定的话从取配置文件的Globalization结点获取,最后默认Encoding.UTF8。

Server.UrlEncode()解码方式,优先从取配置文件的Globalization结点获取,如果配置文件没有的话用Encoding.Default,最后默认用Encoding.UTF8。

Server.UrlEncode()和Server.UrlDecode(),获取编码方式并不一样,两者成对使用结果并不一定正确,这个和我们通常的认识不一致,需要特别注意。

参考:Request 接收参数乱码原理解析你不知道的 页面编码,浏览器选择编码,get,post各种乱码由来

Request 接收参数乱码原理解析一:服务器端解码原理的更多相关文章

  1. Request 接收参数乱码原理解析三:实例分析

    通过前面两篇<Request 接收参数乱码原理解析一:服务器端解码原理>和<Request 接收参数乱码原理解析二:浏览器端编码原理>,了解了服务器和浏览器编码解码的原理,接下 ...

  2. Request 接收参数乱码原理解析二:浏览器端编码原理

    上一篇<Request 接收参数乱码原理解析一:服务器端解码原理>,分析了服务器端解码的过程,那么浏览器是根据什么编码的呢? 1. 浏览器解码 浏览器根据服务器页面响应Header中的“C ...

  3. Request 接收参数乱码原理解析

    起因: 今天早上被同事问了一个问题:说接收到的参数是乱码,让我帮着解决一下. 实际情景: 同事负责的平台是Ext.js框架搭建的,web.config配置文件里配置了全局为“GB2312”编码: &l ...

  4. 详细解析ASP.NET中Request接收参数乱码原理

    起因:今天早上被同事问了一个问题:说接收到的参数是乱码,让我帮着解决一下. 实际情景: 同事负责的平台是Ext.js框架搭建的,web.config配置文件里配置了全局为“GB2312”编码: < ...

  5. 处理request接收参数的中文乱码的问题:

    Ø POST的解决方案: * POST的参数在请求体中,直接到达后台的Servlet.数据封装到Servlet中的request中.request也有一个缓冲区.request的缓冲区也是ISO-88 ...

  6. laravel 请求request 接收参数

    获取请求输入 获取所有输入值 你可以使用 all 方法以数组格式获取所有输入值: $input = $request->all(); 获取单个输入值 使用一些简单的方法,就可以从 Illumin ...

  7. 对小程序的网络请求的封装 wx.request 接收参数修改

    wepy-mall/wxRequest.js at master · dyq086/wepy-mall https://github.com/dyq086/wepy-mall/blob/master/ ...

  8. SprinMVC接收参数乱码解决篇

    1.Spring 默认的字符编码格式为iso-8859-1,为此Spring专门提供了字符过滤器org.springframework.web.filter.CharacterEncodingFilt ...

  9. url参数中带有+号,服务器端解码之后没了

    解决办法: 客户端:encodeURIComponent 服务器端:Uri.UnescapeDataString 参考网址:http://www.cnblogs.com/artwl/archive/2 ...

随机推荐

  1. Android editview 设置只能输入数字、字母和汉字

    Android editview 设置只能输入数字.字母和汉字 当处理密码.昵称等特殊情况的时候,往往需要对输入内容加以限制.对于单纯的一种输入法限制可以对键盘和editview熟悉进行控制,而对于多 ...

  2. 甲乙(数理逻辑)转自http://www.cnblogs.com/devymex/p/3329635.html

    这是一道历史悠久,又很困难的面试题. 你在旁观主持人和甲.乙两个天才数学家玩猜数字游戏.主持人准备了两个数,告知甲乙:这两个数不同,且大于等于1,小于等于30.然后主持人将两数之积告诉甲,把两数之和告 ...

  3. 基于Linux的WebSphere性能调优与故障诊断

    一.关于was数据源等问题的配置 (1)关于was数据源连接池的最大.最小配置多大合适?怎样去计算? (2)关于JVM的配置,64位系统,64位WAS,最值小和最大配置多大最优?怎样去计算? (3)应 ...

  4. 【转】VC中的字符串处理

    http://hi.baidu.com/nmn714/item/ab8d2a96d0f2d6f228164727 貌似不少人刚开始做windows程序时都会纠结在字符串处理上,所以我把关于字符串处理的 ...

  5. 走读openwrt的shell的总结【转】

    原文:http://blog.chinaunix.net/uid-26598889-id-3060543.html ". /etc/diag.sh" 就是将/etc/diag.sh ...

  6. [ASE]项目介绍及项目跟进——TANK BATTLE·INFINITE

    童年的记忆,大概是每周末和小伙伴们围坐在电视机前,在20来寸的电视机屏幕里守卫着这个至今都不知道是什么的白色大鸟. 当年被打爆的坦克数量估计也能绕地球个三两圈了吧. 十几年过去了,游戏从2D-3D,画 ...

  7. 基于ejbca构建独立ca系统

    ejbca,是一个CA(Certificate Authority)系统软件,CA是数字证书认证中心的简称,主要功能是管理数字证书,包括证书的颁发.销毁.更新等,ejbca实现了CA规范,因此可以用来 ...

  8. 软件工程课设day3

    下载昨日新版本程序,完成修复项目的测试. 与组内成员讨论,确认项目新模块功能“吐槽墙”的设计方向与实现形式——因为项目为便捷工具类,社区形式的实现方式与项目本质背道而驰.因此决定以“点击目标课程条目, ...

  9. 必应词典UWP版-开发小结

    摘要 必应词典UWP版已经上线2周了!相信有不少用户都已经体验过了吧!得益于Win10全新.强大的API,新版词典在性能上.UI体验上都有了大幅的提升,今天,小编就为大家讲讲必应词典UWP开发的故事. ...

  10. 一个不陌生的JS效果-marquee,用css3来实现

    关于marquee,就不多说了,可以戳这里. 毕竟他是一个很古老的元素,现在的标准里头也不推荐使用这个标签了.但平时一些项目中会经常碰到这样的效果,每次都是重新写一遍,麻烦! JS类实现marquee ...