前言
因為工作上有需要使用 POST 存取網路上的 JSON 資料,最初得到的方法是使用 CURL,但一開始就失敗了。後來改用程式來測試,希望藉由程式傳回的資料來找出錯誤的原因。結果由 CRUL、Perl、C++、C# 一路用過去,一直都失敗。
最後,在 C# 終於試出一組成功的方法,再利用這個成功的模式一路再試回其他程式,最後只有 CURL 還是失敗,其他都成功了,所以在此記錄結果。
初試啼聲:CURL
最初得到的方法是使用 curl ,格式如下:
curl -X POST http://localhost/test.php -H "content-Type: application/json" -d '[{"src":"如是我聞"}]'
在 Win10 底下也有 curl,測試時卻失敗。使用 Mac 和 Linux 系統的人則告知有得到正確的結果。
首先上網查詢,有人說要把傳出去的字串最外面的單引號改成雙引號,裡面的雙引號要加斜線。
"[{\"src\":\"如是我聞\"}]"
結果還是失敗。
最後我知道這樣是正確的,只是送出去中文變成亂碼,我改用 code page 65001 送出資料也是不行,但若送英文資料則沒有問題。
屢屢失敗
當時只知道失敗,沒有看到失敗原因。但用 Mac 來試則成功。所以就想到自己架一個 Server,檢查由 Mac 傳入的資料和 Win10 傳入的資料有什麼不同,想藉此找到答案。
但一開始架的 Server 端無法處理傳入的 JSON 資料,所以這方法也不成。
再想由 perl 來處理,也許 perl 是跨平台的程式,沒有 Win10 的奇怪問題,結果試了一下,也失敗。
接著試 C++,我是使用 C++ Builder 來寫的,也是希望藉由 C++ 強大的功能,但依然又失敗了。
最後試 C#,C# 也是這陣子才又安裝起來用,好幾年前寫過很小的程式,經驗不多,但覺使用起來很方便。如果不是 C# 是微軟的,又比 C++ 慢,還多一層虛擬機,我大概最想用 C# 吧。
微軟的說明資料又比 C++ Builder 還多,應該比較好查,但依然失敗啊。
最後是上網找了一堆資料,終於使用某一個複雜的範例成功了。
後來又找到一個重點,簡單的範例也可以執行了。
回頭再試 C++,也找到解決的方法。
最後試回 Perl,總算也成功了。
只有 CURL,最後試了試,還是不行,想想就不管了。
PHP Server 端程式
做了一個 PHP 的網頁,主要是用檢查傳進來的資料,看看各程式傳入的數據有什麼問題。
<?php
// 取得輸入資料
$receive = file_get_contents('php://input');
// 印出輸入資料
echo "\n[DATA]:\n";
echo $receive;
// 印出 $_POST 內容
echo "\n\n[POST]:\n";
var_dump($_POST);
// 印出 $_GET 內容
echo "\n[GET]:\n";
var_dump($_GET);
// 將資料轉成 JSON 格式
$data = json_decode($receive);
// 印出 JSON 內容
echo "\n[JSON]:\n";
var_dump($data);
echo "\n[0][src]:" . $data[0]->{"src"};
// 印出 HTTP Header
echo "\n\n[Header]:\n";
foreach (getallheaders() as $name => $value)
echo "$name : $value\n";
?>
C#
所有的程式都會試二種情況,一種是標準的 POST 傳入 "src=如是我聞",另一種是 POST 傳入 JSON 格式 [{"src":"如是我聞"}],原來也想寫 GET ,但這比較簡單,就不寫了。
C# 有三個元件,我一開始有點搞不清楚:
- WebClient
- WebRequest
- HttpRequest
目前的理解是 WebClient 是比較高階的封裝,用起來比較簡單。
WebRequest 可以實作出 HttpWebRequest 和 FtpWebRequest,用起來比較低階、麻煩。
HttpRequest 好像是 Server 端在用的,讓 ASP.NET 在 Web 要求期間讀取用戶端送出的 HTTP 值。所以這個在我們 Client 端應該用不到,這次測試沒用到它。
這是第一次成功的複雜範例,是用 WebRequest 寫的。
var httpWebRequest = (HttpWebRequest)WebRequest.Create("http://localhost/test.php");
httpWebRequest.ContentType = "application/json";
httpWebRequest.Method = "POST";
// 設定要送出的資料
using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
{
string json = "[{\"src\":\"如是我聞\"}]";
streamWriter.Write(json);
streamWriter.Flush();
streamWriter.Close();
}
// 取得傳回的資料
var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
var result = streamReader.ReadToEnd();
// 結果放在 RichTextBox 中
richTextBox1.Text = result;
}
底下是傳回的結果,可以看到第二區塊的 [POST] 並沒有資料,要靠第四區塊的 [JSON] 才能處理。
[DATA]:
[{"src":"如是我聞"}]
[POST]:
array(0) {
}
[GET]:
array(0) {
}
[JSON]:
array(1) {
[0]=>
object(stdClass)#1 (1) {
["src"]=>
string(12) "如是我聞"
}
}
[0][src]:如是我聞
[Header]:
Content-Type : application/json
Host : localhost
Content-Length : 24
Expect : 100-continue
Connection : Keep-Alive
底下是用 WebClient 成功的簡單範例,夠簡單了吧,幸好這個有成功,不然使用上面的就太累了。
WebClient client = new WebClient();
client.Encoding = Encoding.UTF8;
client.Headers.Add("Content-Type", "application/json"); // 底下也可以
//client.Headers[HttpRequestHeader.ContentType] = "application/json";
string result = client.UploadString("http://localhost/test.php", "[{\"src\":\"如是我聞\"}]");
richTextBox1.Text = result;
若要傳送一般的 POST 資料,程式如下。
WebClient client = new WebClient();
client.Encoding = Encoding.UTF8;
client.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
string result = client.UploadString("http://localhost/test.php", "src=如是我聞");
richTextBox1.Text = result;
傳回的結果如下,[DATA] 區和 [POST] 就有資料了。
[DATA]:
src=如是我聞
[POST]:
array(1) {
["src"]=>
string(12) "如是我聞"
}
[GET]:
array(0) {
}
[JSON]:
NULL
[0][src]:
[Header]:
Content-Type : application/x-www-form-urlencoded
Host : localhost
Content-Length : 16
Expect : 100-continue
Connection : Keep-Alive
一開始我依微軟的說明範例去寫,是失敗的,因為範例沒有加 Content-Type。
後來才知道,如果是要傳一般的 POST,則一定要加上
client.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
或
client.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
沒有加上就一定會失敗。
至於測試 JSON 時,知道要加上 Content-Type 為 application/json,但一開始沒有加上
client.Encoding = Encoding.UTF8;
所以才失敗。
不過,若一般 POST 沒加 Encoding 則沒問題。而傳 JSON 資料沒有加 application/json 也可以成功,真是奇怪。
至此,C# 算是成功了,於是回去檢查 C++。
C++
C++ Builder 我是使用 NetHTTPClient 元件和 NetHTTPRequest 元件,這二個要搭配使用。NetHTTPRequest 的 Client 要填入第一個 NetHTTPClient。
要先設定 NetHTTPRequest 接收完畢要處理的事,就是把結果放在 Memo2 上面。
void __fastcall TForm::NetHTTPRequestRequestCompleted(TObject * const Sender, IHTTPResponse * const AResponse)
{
Memo2->Lines->LoadFromStream(AResponse->GetContentStream(),TEncoding::UTF8);
}
一開始用 C++ 測試一般的 POST 是成功的,程式一行就可以了,如下:
NetHTTPRequest->Post("http://localhost/test.php", Memo1->Lines, 0, TEncoding::UTF8);
// 或
NetHTTPRequest->Post("http://localhost/test.php", Memo1->Lines);
Memo1 放資料,只有一行
src=如是我聞
這是因為第二個參數並沒有字串的選項,有 TStrings * , 所以我用 Memo1 當成輸入來源。
第二種程式只有二個參數,沒有指定 UTF8 也可以。
此時的結果如下(省略不重要的區塊了)
[DATA]:
src=%E5%A6%82%E6%98%AF%E6%88%91%E8%81%9E
[POST]:
array(1) {
["src"]=>
string(12) "如是我聞"
}
[Header]:
Connection : Keep-Alive
Content-Type : application/x-www-form-urlencoded; charset=utf-8
User-Agent : Embarcadero URI Client/1.0
Content-Length : 40
Host : localhost
可以發現 C++ 不用輸入 Content-Type,它也會自動送出正確的內容,C# 就是輸在這一點。
但要處理 JSON 資料時,Memo1 的內容改成
[{"src":"如是我聞"}]
也用各種方法把 Content-Type 改成 "application/json",也都沒有用。
後來找到一份資料,它的意思好像是說,它在 TStrings 中取得資料時,還是會依 X=Y 這種格式來處理,並不會因為你改了 Content-Type 而有不同的處理,所以這個方法是失敗的。
後來是使用傳入檔案的方式,程式也是一行:
NetHTTPRequest->Post("http://localhost/test.php", "c:/src.txt");
c:/src.txt 這個檔案是 UTF8 編碼,裡面只是一行
[{"src":"如是我聞"}]
最後也是成功了。
至於其他格式,日後再研究了。
Perl
perl 版直接寫程式了
use utf8;
use HTTP::Request();
use LWP::UserAgent();
use Encode qw/encode decode/;
my $url = "http://localhost/test.php";
my $txt = Encode::encode("utf8","[{\"src\":\"如是我聞\"}]");
my $req = HTTP::Request->new("POST", $url);
$req->content_type("application/json");
$req->content($txt);
# 上面三行也可以改成底下二行
# my $header = ["Content-Type" => "application/json; charset=UTF-8"];
# my $req = HTTP::Request->new("POST", $url, $header, $txt);
my $ua = LWP::UserAgent->new();
my $res = $ua->request($req);
print $res->decoded_content;
如果是傳送一般的 POST ,則中間那一段改成如下。
my $url = "http://localhost/test.php";
my $txt = Encode::encode("utf8","src=如是我聞");
my $req = HTTP::Request->new("POST", $url);
$req->content_type("application/x-www-form-urlencoded");
$req->content($txt);
或
my $url = "http://localhost/test.php";
my $txt = Encode::encode("utf8","src=如是我聞");
my $header = ["Content-Type" => "application/x-www-form-urlencoded; charset=UTF-8"];
my $req = HTTP::Request->new("POST", $url, $header, $txt);
主要的差別是資料的格式為 A=B 這種型式,以及 Content Type 一定要改成 application/x-www-form-urlencoded 才行。
失敗的 CURL
最後再來看失敗的 CURL
先看非中文情況,我下的命令是:
curl -X POST http://localhost/test.php -H "content-Type: application/json" -d "[{\"src\":\"xyz\"}]"
傳回的結果很正常,有順利取得 JSON 資料。
[DATA]:
[{"src":"xyz"}]
[JSON]:
array(1) {
[0]=>
object(stdClass)#1 (1) {
["src"]=>
string(3) "xyz"
}
}
[0][src]:xyz
[Header]:
Host : localhost
User-Agent : curl/7.55.1
Accept : */*
content-Type : application/json
Content-Length : 15
在 code page 為 950 的情況,我下的命令為:
curl -X POST http://localhost/test.php -H "content-Type: application/json" -d "[{\"src\":\"如是我聞\"}]"
底下是全部的結果,可以發現 [DATA] 區有資料傳入,但 [JSON] 區沒有資料。
[DATA]:
[{"src":"如是我聞"}]
[POST]:
array(0) {
}
[GET]:
array(0) {
}
[JSON]:
NULL
[0][src]:
[Header]:
Host : localhost
User-Agent : curl/7.55.1
Accept : */*
content-Type : application/json
Content-Length : 20
再改成 code page 65001,再試一次。
[DATA]:
[{"src":"pOڻD"}]
此時 [DATA] 區變成有亂碼,我想這大概就是 Win10 版的 CURL 一直無法成功的原因。
- 瀏覽次數:15329
發表新回應