お世話になっております。
【 解決したい課題 】
C#のhttpClientを利用してPOSTを行った際、パケットを見ると
ヘッダーとボディーが分割されているように見える。これをhttpclientで同じパケットで送ることは出来ないか?
pythonではpostが成功するのに、c#のhttpclientでは失敗する。何が原因なのか?
ということがお聞きしたい内容です。(2018/03/10 18:28 アップデート)
【 質問者のスキル 】
プログラムを始めて1年程度です。
アプリを専門に開発している部署ではないため、周りに
詳しい方がいません。
【 開発環境 】
現在、C#を利用し、RestAPIを使用したアプリの開発を行っております。
これは外部に出す前に、仕様を確認するための試験的なものです。
私はクライアント側のアプリを作成しており、RestAPIおよびサーバー管理は外部の
企業が担当しております。
クライアントの開発環境は以下のとおりです。
IDE:VisualStudio2015
開発言語:C#
使用ライブラリ:Livet(WPF)
.NET Framework 4.5.0
今回は、methodのうち、GETとPOSTしか使いません。また、POSTで送る内容は
JSONとなります。
ひとまず反応を見るため、次のようなプログラムを作成しました。
なお、プログラムは初心者ですので、至らない点があればご容赦下さい。
C#
1using System.Net.Http; 2using Newtonsoft.Json; 3using Newtonsoft.Json.Linq; 4using Livet; 5using System.Windows; 6 7 8[JsonObject] 9 public class Person 10 { 11 [JsonProperty("age")] 12 public int Age { get; private set; } 13 14 [JsonProperty("name")] 15 public string Name { get; private set; } 16 17 public Person(int age, string name) 18 { 19 this.Age = age; 20 this.Name = name; 21 } 22 } 23 24 public async void Login_test() 25 { 26 27 var person = new Person(20, "test"); 28 var json = JsonConvert.SerializeObject(person); 29 30 using (var client = new HttpClient()) 31 { 32 var content = new StringContent(json, Encoding.UTF8, "application/json"); 33 MessageBox.Show(content.ToString()); 34 var response = await client.PostAsync("http://somehost/someapi", content); 35 } 36 }
この後、responseのデータを取り出すというものです。
最初に、この内容をベースとして、GETのプログラムを組んだのですが、そちらでは問題が
ありませんでした。
ただ、POSTにすると、'System.Net.Http.HttpRequestException' というエラーが発生します。
これに対して、まずはパケット解析ソフトのWiresharkを使い、パケット解析を行いました。
内容を見ると、送信は出来ていましたが、受信が出来ていませんでした。
通常、200や404などが帰ってきますが、それがありません。
次に、RestAPIのツールを使い、そもそもサーバーが正しく動いているか、という確認を行いました。
使用したツールは、Chromeの拡張であるARC、POSTMAN、ウィンドウズアプリであるInsomniaの3種類を
使いました。
その結果、上記3種類では全て通信が正常に行えました。WireSharkで見ても、きちんと返信が
帰ってきていました。ヘッダーを最低限にしたり、足したり、JSONの書き方を変えたりと試したのですが、
最終的にはツールを使うと何をやっても成功し、C#の自作アプリを使うと何をやっても駄目、
という状況に陥りました。
ひとまず、ツールで成功しているわけですから、ツールと同じパケットを送信すれば受け付けて
もらえるだろうと考え、JSONのフォーマットやタグなどを可能な限り内容を近づけてC#から送信したのですが、
何をやっても成功しませんでした。
そうなると、やはりコード自体がおかしいのでは、ということになりますが、何故かパケット解析ツールの
Fiddlerを立ち上げ、F12キーでパケットをキャプチャしている時は正常に動作します。
200が返され、こちらの欲しいデータが帰ってきます。
パケットキャプチャを停止すると、またデータも返信も何も帰ってこない状態に陥ります。
そのうち、送信パケットについて、成功しているパターンと成功していないパターンを比較すると、
成功していないパターンではWireshark上でReassembled TCPという文字列があることがわかりました。
下図は、失敗しているパケットをキャプチャした際の画像です。
(キャプチャは例で、先に挙げたプログラムの内容と全くの同一ではありません。)
初心者ですので正確なことは分かりませんが、ヘッダーとボディーを別パケットとして送信しているのでは、
と見ています。成功したPOSTのパケットを見ると、これらの表記はありません。
なお、PCのMTUは1500程度になっております。
この見解が正しいのかは分かりませんが、まず、この表記が出ない設定で通信を行い、状況に変化があるか
を見たいと考えております。
上図のような表記を出さず、httpClientでPOST命令を出す方法をご存じの方がいれば、
ご教示下さい。
今回はデモ用であるため、最悪、Fiddlerを立ち上げ、パケットキャプチャを行いながらでもOKでは
あるのですが、可能であれば単体で動作させたいと思います。
以上、よろしくお願い致します。
以降、追記部分となります。
2018/03/08 9:17 追記
1,ファイアウォールとアンチウイルスソフトについて
先程、開発用PCにインストールされている該当のソフトを一時的にオフにし、さらにウィンドウズのファイアウォールも
オフにしましたが、状況に変化はございませんでした。
2,エラーメッセージの詳細
本日あらためてトライしたところ、次のようなエラーとなりました。
昨日と異なるエラーとなっています。(名前等は修正してあります。)
System.Threading.Tasks.TaskCanceledException はユーザー コードによってハンドルされませんでした。
HResult=-2146233029
Message=タスクが取り消されました。
Source=mscorlib
StackTrace:
場所 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
場所 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
場所 System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
場所 origin2.ViewModels.MainWindowViewModel.<Login_test>d__5.MoveNext() 場所 C:\Users\dummy\Documents\sample_prog\test\test\ViewModels\MainWindowViewModel.cs:行 253
InnerException:
Wiresharkでパケット解析をしながら行ったのですが、POSTは出来てきますが、その返事がないという状況です。
2018/03/08 10:16 追記
筐体の問題およびネットワークの問題なのか、プログラム言語の問題なのかを
切り分けるため、次の作業を行いました。
1、プロキシの確認
2、別言語(Python)での確認
1、についてですが、こちらは設定されておりませんでした。ただし、社内ネットワークであるため、何らかの制限がかかっている可能性は否めません。
実際に、一部のサイトは閲覧制限がかかっているため、閲覧できないサイトもあります。
2、1、の結果を受けて、問題の切り分けのため、別言語での検証を行いました。
開発環境はVisualStudio2017と、Anacondaを利用したpython3.6となっています。
Python
1import urllib.request, json 2 3if __name__ == '__main__': 4 url = "http://dummy" 5 method = "POST" 6 headers = {"Content-Type" : "application/json"} 7 8 # PythonオブジェクトをJSONに変換する 9 obj = {"test" : "test", 123 : 123} 10 json_data = json.dumps(obj).encode("utf-8") 11 12 # httpリクエストを準備してPOST 13 request = urllib.request.Request(url, data=json_data, method=method, headers=headers) 14 with urllib.request.urlopen(request) as response: 15 response_body = response.read().decode("utf-8") 16
JSONは適当に入力したため、正常なデータは帰ってきませんが、
構文が違うという400(Bad Request)が帰ってきました。この後、jsonを
正しく設定すると、必要なデータが取得できました。
このことから、ネットワーク自体に問題は無いものと思われます。
Wiresharkで確認すると、reassembled TCP、及びTCP Segmentsの項目がありました。
そのため、パケットが別れているから駄目だった、という私の考えは間違っていたことが
判明しました。
2018/03/10 18:28 追記
いろいろな方のご意見を受けて、改めて調査を行いました。
① 問題がネットワークにあるかどうか
自宅を含めた別ネットワークについては一度確認していることですが、
改めて調査を行いました。
なお、自分のパソコン、ネットワークはプロキシがないことを確認しています。
会社のパソコン、ネットワークはプロキシは不明です。
確認項目
0. ツール(Insomnia)では返信があるか
0. 自作のc#アプリでは返信があるか
0. 自作c#で別API(今回はニコニコ動画の情報取得APIを利用)では返信があるか
-
会社のパソコン & 会社のネットワーク → 1,OK 2,NG 3,OK
-
会社のパソコン & 自分のテザリング → 1,OK 2,NG 3,OK
-
自宅のパソコン & 自宅のネットワーク → 1,OK 2,NG 3,OK
-
自宅のパソコン & 自分のテザリング → 1,OK 2,NG 3,OK
-
自宅のパソコン & 自分のポケットWIFI → 1,OK 2,NG 3,OK
-
自宅のパソコン & 自分のポケットWIFI(一度初期化してSIMだけ再設定) → 1,OK 2,NG 3,OK
となりました。この結果から、ネットワークの可能性は低いと判断しました。
② ソケットを食いつぶしているかどうか
日をまたいで、起動直後に試しても、状況は変わりませんでした。
③ Fiddlerはパケットに対して何か影響を与えるのか
調査の結果、Fiddlerのインストール直後の状態では、パケットに対して影響がありました。
わかりやすいところで言うと、Fiddlerを介すると、Wireshark上でReassembled TCP
が無くなることが分かりました。
④ 同一パケットでbodyを送ると成功するのか
Fiddlerの結果から、改めてbodyとheaderを同時に送りどうなるか
確認したくなったため、無理やりですが、実際に試してみました。
結論から言えば、成功しました。
C#で、以下のようなコードでPOSTを行いました。
反応が見たいだけですので、今は命令を出すところしか構築していません。
C#
1using (var socket = new TcpClient()) 2 { 3 4 string json = "リクエストボディ部"; 5 6 socket.Connect("ip", port); 7 var body = Encoding.UTF8.GetBytes(json); 8 var bodyLength = Encoding.UTF8.GetByteCount(json); 9 10 var headerContent = new StringBuilder(); 11 headerContent.AppendLine("POST /v1/dummy HTTP/1.1"); 12 headerContent.AppendLine("Accept: */*"); 13 headerContent.AppendLine("Host: " + "ip:port"); 14 headerContent.AppendLine("Content-Type: application/json; charset=utf-8"); 15 headerContent.AppendLine("Content-Length: " + bodyLength); 16 headerContent.AppendLine("Connection: Close"); 17 headerContent.AppendLine(); 18 headerContent.AppendLine(json); 19 var headerString = headerContent.ToString(); 20 var header = Encoding.UTF8.GetBytes(headerString); 21 var headerLength = Encoding.UTF8.GetByteCount(headerString); 22 23 using (var stream = socket.GetStream()) 24 { 25 stream.Write(header, 0, headerLength); 26 //stream.Write(body, 0, bodyLength); 27 } 28 }
いろいろ粗が目立ちますが、ひとますこれでテストしました。
ヘッダーを自分で書いてそのまま投げるイメージです。
こうすると、末尾に無理やりJSONをつけて渡すことができます。
この方法だと、Fiddlerを介さずとも100%返信が来るようになりました。
(Wireshark上で確認)
以上のことから、ヘッダーとボディをひとつのパケットで送り出すことが
鍵である可能性が高いと考えております。
(pythonだと分割しても大丈夫なようですが、c#だと駄目、というのは解せない
ですが、正常に通信できる方法が確立した後に考えたいと思います。)
無理やりの方法は判明しましたが、可能であればデバッグの手間を省くため、
httpclientなどの既存ライブラリを使いたく存じます。
やり方をご存知の方がいましたらご教示ください。
回答3件
あなたの回答
tips
プレビュー