阅读本文之前先了解一下:什么时候需要使用UrlEncode和urlendecode函数?
作者使用谷歌浏览器,通过按F12来分析和操作第三方网站的http协议界面。
00-1010运维小哥偶尔用某外包公司的网站系统做设备录入。过程很简单:
输入设备信息
输入设备的基本信息。有7或8个字段需要输入,然后单击保存按钮。基本信息保存成功,进入设备类型选择操作,点击生成设备标识按钮;成功生成设备标识,并输入与设备相关联的模块信息。简单设备只需要输入2个模块,复杂设备有6个模块,每个模块有3到4个字段可以输入。最后,单击保存。如果一件装备成功进入,单身多年的手速可能不需要几分钟。其实这没什么。
突然领导说有1000台设备要造?运维小哥哭了,该开发商上场了:
准备一个Excel模板进行运维,输入1000条需要录入的设备基本信息和设备类型信息。这个工作量不大,就半天,最多一天;开发一个C/S客户端小工具,在程序中根据业务需求配置模块进入规则;当在程序执行过程中输入设备时,生成的设备标识符与该设备相关联;所有录入完成,并提供Excel导出,可以导出所有基本设备信息和生成的设备标识,工作完成。经过几天的开发工作,开发小哥把精心打磨的小玩意交给运维小哥,运维小哥使用后投以赞许的目光。
00-1010有点啰嗦。在开发这个小工具的时候,我的小哥哥遇到了一个问题:
Xxx接口
这是一个接口的信息。内容类型是应用程序。以下参数使用表单数据,即参数使用urlencode,例如编码前的参数:
内容' : ' { ' AP _ Name ' : ' HK _ 7889 ',' IP':'192.168.0.1'} '
编码后(可通过此在线网址编码和解码工具验证):
内容' : ' { AP _ Name:" " HK _ 7889 "," IP":"92.168.0.1"} '
使用Postman测试时,参数没有使用UrlEncode,接口测试成功。开发这个小工具时,有三个接口是相似的,没有执行UrlEncode操作:
var client=NewRestClient(' http://admin . lqclass.com/API/device ');
客户。超时=-1;
varrequest=newRestRequest(方法。POST);
请求。AddHeader('Content-Type ',' application/x-www-form-URL encoded ');
请求。AddParameter('Content ',' { ' AP _ Name ' : ' HK _ 7889 ', ' IP ' : ' 92 . 168 . 0 . 1 ' } ');
IRestResponseresponse=客户端。执行(请求);
控制台。WriteLine(响应。内容);
但是会遇到稍微复杂的界面,例如截图中的参数是:
内容' :'{'AP_Name':'HK_7889 ',' IP':'192.168.0.1 ',' Module ' :[{ ' M _ Name ' : ' camera i0 ',' desc ' : ' camera i0 ',' AP _ PUID ' : ' 54632325461320320 ' },{ ' M _ Name ' : ' cameri1
数值格式清晰可见。模块是与设备相关的模块信息:
{
AP_Name':'HK_7889 ',
IP':'192.168.0.1 ',
模块' :[
{
M _ Name ' : ' cameri0 ',
Desc': '摄像机,
PUID':'54632325461320320 '
},
{
M _ Name ' : ' cameri1 ',
Desc': '摄像机1 ',
PUID':'54636325461320320 '
},
{ "M_Name": "cameri2", "Desc": "cameri2", "AP_PUID": "54632325421320320" } ] }实际UrlEncode后的参数为:
"Content":"%7B%22AP_Name%22%3A%22HK_7889%22%2C%22IP%22%3A%22192.168.0.1%22%2C%22Module%22%3A%22%255B%257B%2522M_Name%2522%253A%2522cameri0%2522%252C%2522Desc%2522%253A%2522cameri0%2522%252C%2522AP_PUID%2522%253A%252254632325461320320%2522%257D%252C%257B%2522M_Name%2522%253A%2522cameri1%2522%252C%2522Desc%2522%253A%2522cameri1%2522%252C%2522AP_PUID%2522%253A%252254636325461320320%2522%257D%252C%257B%2522M_Name%2522%253A%2522cameri2%2522%252C%2522Desc%2522%253A%2522cameri2%2522%252C%2522AP_PUID%2522%253A%252254632325421320320%2522%257D%255D%22%7D"本来一般接口,如上面成功执行的C#代码那般直接未UrlEncode调用是没问题的。
但这个接口调用,服务器返回错误信息:“xxx解析失败”,调用代码如下:
var client = new RestClient("http://admin.lqclass.com/api/device"); client.Timeout = -1; var request = new RestRequest(Method.POST); request.AddHeader("Content-Type", "application/x-www-form-urlencoded"); request.AddParameter("Content", "{"AP_Name":"HK_7889","IP":"192.168.0.1","Module":[{"M_Name":"cameri0","Desc":"cameri0","AP_PUID":"54632325461320320"},{"M_Name":"cameri1","Desc":"cameri1","AP_PUID":"54636325461320320"},{"M_Name":"cameri2","Desc":"cameri2","AP_PUID":"54632325421320320"}]}"); IRestResponse response = client.Execute(request); Console.WriteLine(response.Content);两处调用代码哪里不同?只是Content值不一样,最后怀疑是不是需要手动进行UrlEncode?又不是url参数,为啥需要编码呢?不管啦,先编码了再说。
问题解决
参数编码后,调用:
var client = new RestClient("http://admin.lqclass.com/api/device"); client.Timeout = -1; var request = new RestRequest(Method.POST); request.AddHeader("Content-Type", "application/x-www-form-urlencoded"); request.AddParameter("Content", "%7B%22AP_Name%22%3A%22HK_7889%22%2C%22IP%22%3A%22192.168.0.1%22%2C%22Module%22%3A%22%255B%257B%2522M_Name%2522%253A%2522cameri0%2522%252C%2522Desc%2522%253A%2522cameri0%2522%252C%2522AP_PUID%2522%253A%252254632325461320320%2522%257D%252C%257B%2522M_Name%2522%253A%2522cameri1%2522%252C%2522Desc%2522%253A%2522cameri1%2522%252C%2522AP_PUID%2522%253A%252254636325461320320%2522%257D%252C%257B%2522M_Name%2522%253A%2522cameri2%2522%252C%2522Desc%2522%253A%2522cameri2%2522%252C%2522AP_PUID%2522%253A%252254632325421320320%2522%257D%255D%22%7D"); IRestResponse response = client.Execute(request); Console.WriteLine(response.Content);哈哈,成功了,这里简单猜测下:别人的服务对接收的参数可能做了UrlDecode操作。
其实中间还做了一个参数的UrlEncode操作,即下面的Module参数值:
"Content":{"AP_Name":"HK_7889","IP":"192.168.0.1","Module":[{"M_Name":"cameri0","Desc":"cameri0","AP_PUID":"54632325461320320"},{"M_Name":"cameri1","Desc":"cameri1","AP_PUID":"54636325461320320"},{"M_Name":"cameri2","Desc":"cameri2","AP_PUID":"54632325421320320"}]}第一次UrlEncode,即先对Module的值进行UrlEncode:
"Content":{"AP_Name":"HK_7889","IP":"192.168.0.1","Module":%5B%7B%22M_Name%22%3A%22cameri0%22%2C%22Desc%22%3A%22cameri0%22%2C%22AP_PUID%22%3A%2254632325461320320%22%7D%2C%7B%22M_Name%22%3A%22cameri1%22%2C%22Desc%22%3A%22cameri1%22%2C%22AP_PUID%22%3A%2254636325461320320%22%7D%2C%7B%22M_Name%22%3A%22cameri2%22%2C%22Desc%22%3A%22cameri2%22%2C%22AP_PUID%22%3A%2254632325421320320%22%7D%5D}第二次UrlEncode即是上面成功的参数方式了,对整个Content的值进行UrlEncode,看上面成功的参数,不重复贴了。
最后总结
抓别人数据包时,不要凭印象、已有知识判定该怎么怎么做,比如前面的参数,不使用UrlEncode时,调用成功了,其他包我是否也沿用相同的方式使用就正确呢?搞不定时,多尝试猜测的方法。
总结:“管他的,干就是了”。
本文使用的UrlEncode C# 代码:
public static string UrlEncode(string str) { StringBuilder sb = new StringBuilder(); byte[] byStr = System.Text.Encoding.UTF8.GetBytes(str); //默认是System.Text.Encoding.Default.GetBytes(str) for (int i = 0; i < byStr.Length; i++) { sb.Append(@"%" + Convert.ToString(byStr[i], 16)); } return (sb.ToString()); }