Skip to Content

Perl C++ C# 使用 POST GET 存取 JSON 等網路資源

 

前言

因為工作上有需要使用 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 的網頁,主要是用檢查傳進來的資料,看看各程式傳入的數據有什麼問題。

http://localhost/test.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 一直無法成功的原因。

 

回應

發表新回應

這個欄位的內容會保密,不會公開顯示。
  • 自動將網址與電子郵件地址轉變為連結。
  • 自動斷行和分段。
  • 可使用的 HTML 標籤:<a> <address> <b> <blockquote> <br> <caption> <center> <cite> <code> <dd> <del> <div> <dl> <dt> <em> <h1> <h2> <h3> <h4> <h5> <h6> <hr> <i> <img> <ins> <li> <ol> <p> <pre> <span> <strike> <strong> <sub> <sup> <table> <tbody> <td> <th> <tr> <u> <ul>
    Allowed Style properties: background, background-attachment, background-color, background-image, background-position, background-repeat, border, border-bottom, border-bottom-color, border-bottom-style, border-bottom-width, border-collapse, border-color, border-left, border-left-color, border-left-style, border-left-width, border-right, border-right-color, border-right-style, border-right-width, border-spacing, border-style, border-top, border-top-color, border-top-style, border-top-width, border-width, bottom, caption-side, clip, color, direction, empty-cells, font, font-family, font-size, font-size-adjust, font-stretch, font-style, font-variant, font-weight, height, left, letter-spacing, line-height, list-style, list-style-image, list-style-position, list-style-type, margin, margin-bottom, margin-left, margin-right, margin-top, max-height, max-width, min-height, min-width, overflow, padding, padding-bottom, padding-left, padding-right, padding-top, right, table-layout, text-align, text-decoration, text-indent, text-transform, top, unicode-bidi, vertical-align, white-space, width, word-spacing, z-index

更多關於格式選項的資訊

CAPTCHA
驗證碼只有阿拉伯數字, 這是躲廣告用的, 麻煩你輸入了.
Image CAPTCHA
Enter the characters shown in the image.


story | about seo