最近遇到的問題記錄:UrlEncode、UrlDecode

最近遇到的問題記錄:UrlEncode、UrlDecode

簡單分享

最後更新 2021/1/9 下午5:03
沙漠尽头的狼
預計閱讀 7 分鐘
分類
.NET
標籤
.NET C# Web API URL解碼 URL編碼

本文閱讀前了解知識:什麼時候需要使用 UrlEncode 和 UrlDecode 函數

作者使用谷歌瀏覽器,透過按下 F12 對第三方網站 http 協定的介面抓包進行分析操作。

場景

運維小哥哥偶爾使用某某外包公司的網站系統,做設備錄入工作,流程簡單:

錄入設備資訊

  1. 錄入設備基本資訊,有 7、8 個欄位需要輸入,然後點選儲存按鈕;
  2. 基本資訊儲存成功,進入設備類型選擇操作,然後點選生成設備識別碼按鈕;
  3. 設備識別碼生成成功,錄入設備關聯的模組資訊,簡單設備只需要錄入 2 條模組,複雜的設備有 6 條模組,每個模組有 3、4 個欄位需要輸入,最後點選儲存。

一條設備錄入成功,單身多年的手速可能也花不了幾分鐘,其實這也沒啥。

突然領導說有 1000 個設備需要搞?運維小哥哥哭了 😂,這時就該開發人員上場了:

  1. 運維準備一個 Excel 模板,輸入需要錄入的 1000 個設備基本資訊、設備類型資訊,這個工作量不大,就半天吧,最多一天工作量;
  2. 開發做個 C/S 用戶端小工具,程式中按業務要求配置模組錄入規則;
  3. 程式執行過程中錄入一個設備就把生成的設備識別碼與設備關聯;
  4. 全部錄入完成,提供一個 Excel 匯出,可將設備基本資訊、生成的設備識別碼全部關聯匯出,工作完成。

經過幾天的開發工作,開發哥哥將精心打磨的小工具交給運維小哥,運維小哥哥使用後投來了讚許的目光...

問題

前面鋪墊的話有點囉嗦了,開發這個小工具時,開發小哥遇到一個問題:

xxx 介面

xxx介面

這是某個介面的資訊,Content-Typeapplication/x-www-form-urlencoded,下面參數使用的Form Data,即參數使用了UrlEncode,比如未編碼前的一個參數:

"Content":"{"AP_Name":"HK_7889","IP":"192.168.0.1"}"

編碼後(可以使用這個線上 URL 編碼解碼工具驗證):

"Content":"%7B%22AP_Name%22%3A%22HK_7889%22%2C%22IP%22%3A%2292.168.0.1%22%7D"

使用Postman測試時,未對參數使用UrlEncode,介面測試成功,開發這個小工具時,有 3 個介面都是類似的,未進行UrlEncode操作:

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\":\"92.168.0.1\"}");
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);

但遇到稍微複雜一點的介面,比如截圖中的參數為:

"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"}]}"

Content值格式化看得清楚一點,Module是設備關聯的模組資訊:

{
  "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後的參數為:

"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());
}
繼續探索

延伸閱讀

更多文章
同分類 / 同標籤 2024/1/19

基於 .NET 的 FluentValidation 驗證教學

FluentValidation 是一個基於 .NET 開發的驗證框架,開源免費,而且優雅,支援鏈式操作,易於理解,功能完善,還可與 MVC5、WebApi2 和 ASP.NET CORE 深度整合,組件內提供十幾種常用驗證器,可擴展性好,支援自訂驗證器,支援本地化多語言。

繼續閱讀