C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

HttpClientでのmultipart/mixed送信

[トピック内 10 記事 (1 - 10 表示)]  << 0 >>

■86305 / inTopicNo.1)  HttpClientでのmultipart/mixed送信
  
□投稿者/ さえ (1回)-(2018/01/15(Mon) 23:59:06)

分類:[.NET 全般] 

プログラミング初心者です。
あるWEBAPIを利用するため、C#でコードを組みたいと考えています。

APIの仕様は以下の通りです。
・通信プロトコル HTTP/1.1
・HTTP Method:POST
・Content-Type: multipart/mixed
・Content-Disposition:mixed
・Content-Type: text/csv
(複数のcsvファイルをアップロードし、文字コードはShift_JISと指定されています)

下記のコードを書いてみたのですが、上手く結果を取得できません。
通信は出来ているようで応答は帰ってくるのですが、APIが返すメッセージは
「ファイルがありません」という内容で、ファイルが認識されていないようです。

アップロードするcsvファイルはShift_JISで保存しているつもりですが、文字コードの問題かもしれません。
HttpClientの使い方やヘッダの設定値に問題があるかもしれません。
自分では解決策が全く分からず困っています。。間違っている点やヒント等でもご指摘いただけると大変有難いです。
どうぞよろしくお願いいたします。

void test()
{
var content = new MultipartFormDataContent();
content.Headers.ContentType.MediaType = "multipart/mixed";
var list = new List<string>();
list.Add("C:\A");
list.Add("C:\B");
list.Add("C:\C");

foreach (var s in list)
{
var fileContent = new StreamContent(File.OpenRead(s));
fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("mixed")
{
Name = Path.GetFileName(s)
};
fileContent.Headers.ContentType = new MediaTypeHeaderValue("text/csv");
content.Add(fileContent);
}

var httpClient = new HttpClient();
string url = @"送信先のurl";
HttpResponseMessage response = httpClient.PostAsync(url, content).Result;

string test = response.Content.ReadAsStringAsync().Result;

}


引用返信 編集キー/
■86307 / inTopicNo.2)  Re[1]: HttpClientでのmultipart/mixed送信
□投稿者/ Hongliang (600回)-(2018/01/16(Tue) 09:44:07)
ネットワーク関連のプログラミングを行う場合、パケットキャプチャソフトを利用して、どんなデータが送信されているか/受信しているか、を確認しながら作業することをお勧めします。

> ・Content-Disposition:mixed

これがちょっとよくわからない仕様です。
https://wiki.suikawiki.org/n/multipart%2Fform-data#header-section-%E6%A7%8B%E6%96%87%E2%80%A8%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E5%80%8B%E6%95%B0%E2%80%A8multipart%2Fmixed-%E6%96%B9%E5%BC%8F
ここによると、multipart/mixedの場合Content-Dispositionの値はattachmentまたはfileとなるらしいですが。
また、ファイル名も、filenameパラメータを使用するようです。

この辺り、もう一度API仕様の確認をされては。


> var content = new MultipartFormDataContent();
> content.Headers.ContentType.MediaType = "multipart/mixed";

間違いではありませんが、new MultipartContent("mixed")とすれば、
改めてMediaTypeを書き換える必要はなくなります。
引用返信 編集キー/
■86320 / inTopicNo.3)  Re[2]: HttpClientでのmultipart/mixed送信
□投稿者/ さえ (2回)-(2018/01/18(Thu) 00:07:03)
No86307 (Hongliang さん) に返信

ご回答どうもありがとうございます。大変助かります。
基本的なことが分かっておらず申し訳ありませんが、
下記また質問させていただけないでしょうか。どうぞよろしくお願いいたします。

> ネットワーク関連のプログラミングを行う場合、パケットキャプチャソフトを利用して、どんなデータが送信されているか/受信しているか、を確認しながら作業することをお勧めします。

早速Wiresharkをインストールしました。パケットを見てみましたが、特に問題なく、
csvファイル本体部分はs-jisですが元のファイルのバイナリデータと比べると一致しているように見えました。
API仕様は下記なのですが、2ページ目の要求例を見ると、Host、Expect、Connectionがありません。これらのパラメータを送信していることが問題となっている可能性は考えられますでしょうか。

http://www.kenken.go.jp/becc/documents/building/model_API_171002.pdf

> この辺り、もう一度API仕様の確認をされては。
見方が間違っているかもしれませんが、上記仕様では
Content-Disposition: mixed;
となっています。古い仕様なのでしょうか。。

> 間違いではありませんが、new MultipartContent("mixed")とすれば、
> 改めてMediaTypeを書き換える必要はなくなります。
わざわざMultipartFormDataContentを使用する必要はなかったのですね。大変勉強になりました。

引用返信 編集キー/
■86321 / inTopicNo.4)  Re[3]: HttpClientでのmultipart/mixed送信
□投稿者/ はるまきとかげ (6回)-(2018/01/18(Thu) 02:04:40)
ブラウザからリクエスト送りまくってみたけどダメでした
Aを指定してなくてもAのファイルをアップロードしてくださいって出るんですよね
なんだろこれ、意味が分からない

サービス提供してるところに問い合わせたが早いかもしれないですよ
引用返信 編集キー/
■86323 / inTopicNo.5)  Re[3]: HttpClientでのmultipart/mixed送信
□投稿者/ Hongliang (602回)-(2018/01/18(Thu) 09:43:15)
> API仕様は下記なのですが、2ページ目の要求例を見ると、Host、Expect、Connectionがありません。これらのパラメータを送信していることが問題となっている可能性は考えられますでしょうか。

Host, Connectionはまず問題ないでしょう。
Exceptは消した方がいいかもしれません。HttpClientオブジェクトの.DefaultRequestHeaders.ExceptContinueで操作できます。

正常にデータを送信できるクライアントがあれば、それの送信内容と比較できるのですが。
引用返信 編集キー/
■86325 / inTopicNo.6)  Re[1]: HttpClientでのmultipart/mixed送信
□投稿者/ 魔界の仮面弁士 (1540回)-(2018/01/18(Thu) 13:06:32)
2018/01/18(Thu) 13:13:23 編集(投稿者)

No86305 (さえ さん) に返信
> list.Add("C:\A");
これは
 list.Add("C:\\A");
もしくは
 list.Add(@"C:\A");
にする必要がありますね。


> list.Add("C:\B");
> list.Add("C:\C");
B1 や C4 ならありますが、B や C は未定義なのでは?


それと、VirtualStore 化の影響を受ける可能性を排除するため、
C:\ ルートに置いたファイルを読み書きすることは
できるだけ避けた方が良いでしょう。


> APIが返すメッセージは
> 「ファイルがありません」という内容で、ファイルが認識されていないようです。

もしかしたら、これは Web API 側の不具合かもしれません。

v204 サーバーを相手に、computeFromInputSheets API を投げて
成功するパターンと、C# からのリクエストを比較したのですが、
ServerXMLHTTP 版から正常に送出されていたパターンでは、
 ・文字集合が Shift_JIS なファイルを UTF-8 に再エンコードして送出している。
 ・要求ボディの冒頭に、空の改行が一つ挿入されている。
という要求が発行されていました。

試しに、冒頭の \r\n を取り除いて送信してみると、
A ファイルを示す BasicInformationValidationResult エントリに
 「ファイルがありません。アップロードしてください。」
に相当する JSON メッセージが返却される現象が再現しました。

昼休みに軽く実験してみただけなので、確信があるわけではないですが。
引用返信 編集キー/
■86326 / inTopicNo.7)  Re[2]: HttpClientでのmultipart/mixed送信
□投稿者/ 魔界の仮面弁士 (1回)-(2018/01/18(Thu) 13:29:13)
2018/01/18(Thu) 13:29:52 編集(投稿者)

No86325 (魔界の仮面弁士) に追記
>  ・要求ボディの冒頭に、空の改行が一つ挿入されている。
> という要求が発行されていました。

それとバウンダリ文字列については
 「Content-Type: multipart/mixed; boundary="e16a797c-4a27-49a4-86b7-c71d92c43758"」
の形式ではなく
 「Content-Type: multipart/mixed; boundary=e16a797c-4a27-49a4-86b7-c71d92c43758」
で送出しないと、このサーバーには受け付けてもらえないかも。
引用返信 編集キー/
■86343 / inTopicNo.8)  Re[3]: HttpClientでのmultipart/mixed送信
□投稿者/ さえ (3回)-(2018/01/22(Mon) 01:58:36)
はるまきとかげ様、Hongliang様、魔界の仮面弁士様

ご指摘どうもありがとうございます。大変勉強になり、有難いです。
ファイルの操作やヘッダの操作など、基本的な問題で申し訳ありませんが、これから勉強していきたいと思っています。
また、サービス提供元は問合せを受け付けていないようでした。

情報が小出しになってしまいすみませんが、正常に動作するVBAのサンプルが公開されていました。
サンプルと自作C#のパケットキャプチャの結果および修正した自作C#コードを下記に記載します。
APIが返す結果は変わらず「ファイルがありません」となります。

魔界の仮面弁士様にご指摘頂いた通り、Shift_JISのcsvはUTF-8 に再エンコードして送出されているようでしたので、
元のファイルの方をutf-8に変換しています。

サンプルとC#の結果の相違点は、Content-Length(変換方法の問題?)とバウンダリ文字のように見えます。
バウンダリにダブルクォーテーションを付けない形で設定する方法はどうすればよいでしょうか。

何度も申し訳ありませんが、どうぞよろしくお願いいたします。



***正常に動作するサンプルの送出データ***
POST /api/v1/ConvertToWebInput HTTP/1.1
Connection: Keep-Alive
Content-Type: multipart/mixed; boundary=================12345678987654321
Accept: */*
Accept-Language: ja
User-Agent: Mozilla/4.0 (compatible; Win32; WinHttp.WinHttpRequest.5)
Content-Length: 13795
Host: model.app.lowenergy.jp


--================12345678987654321
Content-Disposition: mixed; name="A"
Content-Type: text/csv; charset=utf-8

*CSVの中身


***自作C#の送出データ***
POST /api/v1/ConvertToWebInput HTTP/1.1
Accept-Language: ja
Accept: */*
User-Agent: Mozilla/4.0 (compatible; Win32; WinHttp.WinHttpRequest.5)
Content-Type: multipart/mixed; boundary="================12345678987654321"
Host: model.app.lowenergy.jp
Content-Length: 13793
Connection: Keep-Alive

--================12345678987654321
Content-Disposition: mixed; name="A"
Content-Type: text/csv; charset=utf-8

*CSVの中身


***自作C#コード***
            var files = new List<string>();
            files.Add(@"C:\test\A");
            
            var content = new MultipartContent("mixed", "================12345678987654321");

            foreach (var s in list)
            {
                var fileContent = new StreamContent(File.OpenRead(s));
                fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("mixed")
                {
                    Name = "\"" + Path.GetFileName(s) + "\""
                };
                fileContent.Headers.ContentType = new MediaTypeHeaderValue("text/csv"){ CharSet = Encoding.UTF8.WebName };
                content.Add(fileContent);
            }

            var httpClient = new HttpClient();
            httpClient.DefaultRequestHeaders.Add("Accept-Language", "ja");
            httpClient.DefaultRequestHeaders.Add("Accept", "*/*");
            httpClient.DefaultRequestHeaders.Add("User-Agent", "Mozilla/4.0 (compatible; Win32; WinHttp.WinHttpRequest.5)");

            string url = @"http://model.app.lowenergy.jp/api/v1/ConvertToWebInput";
            httpClient.DefaultRequestHeaders.ExpectContinue = false;
            HttpResponseMessage response = httpClient.PostAsync(url, content).Result;

            string test = response.Content.ReadAsStringAsync().Result;

引用返信 編集キー/
■86345 / inTopicNo.9)  Re[4]: HttpClientでのmultipart/mixed送信
□投稿者/ 魔界の仮面弁士 (1542回)-(2018/01/22(Mon) 11:29:31)
No86343 (さえ さん) に返信
> foreach (var s in list)
「foreach (var s in files)」ですよね。

ところで、HttpClient には下記のような問題があるのでご注意を。
https://qiita.com/nskhara/items/b7c31d60531ffbe29537


> ***正常に動作するサンプルの送出データ***
> Content-Length: 13795
> ***自作C#の送出データ***
> Content-Length: 13793

要求ボディのサイズ(Content-Length ヘッダー)が 2 バイト違っていますが、
これは先頭の CR LF の有無によるものです。


VBA バージョンにおいて、その 冒頭の CR LF を削って送出した場合、
残念ながら No86325 で示したように
 「ファイルがありません。アップロードしてください。」
が再現することになります。本来、冒頭の CR LF は無くても構わないはずなので、
このような動作になっているのは、Web API 側の実装に問題があるのだと推察します。


マルチパートな HTTP では、preamble (および epilogue) は
空で送信されるのが通例ですし、先頭が空の preamble である場合、
冒頭の CR LF は省略可能だと思うのですけれどね…。

https://www.ietf.org/rfc/rfc2046.txt
》multipart-body := [preamble CRLF]
》                  dash-boundary transport-padding CRLF
》                  body-part *encapsulation
》                  close-delimiter transport-padding
》                  [CRLF epilogue]
》

https://wiki.suikawiki.org/n/境界文字列
》古い版の MIME では一番最初の本体部分の前の境界でも 
》--boundary の前の CRLF が必須とされていましたが、
》今日では不具合であったと考えられています。


> Shift_JISのcsvはUTF-8 に再エンコードして送出されているようでしたので、
PDF に記載された要求の例では
 Content-Type: text/csv
となっているのに、VBA 実装では
 Content-Type: text/csv; charset=utf-8
となっていますし、仕様か実装のいずれかが間違っていそう。


> バウンダリにダブルクォーテーションを付けない形で設定する方法はどうすればよいでしょうか。

VBA 版と同様、要求電文を自前で組み立てないと駄目かも。
以下、Task 化してませんが参考までに。

static void Main()
{
  var files = new List<string>();
  files.Add(@"C:\temp\A");

  string url = @"http://model.app.lowenergy.jp/api/v1/ConvertToWebInput";
    
  var webReq = System.Net.HttpWebRequest.CreateHttp(url);
  webReq.Method = HttpMethod.Post.Method;

  // var boundary = "================12345678987654321";
  var boundary = "==" + Guid.NewGuid().ToString("D");

  //バウンダリを quoted-string にすると失敗する
  //webReq.ContentType = "multipart/mixed; boundary=\"" + boundary + "\"";

  //バウンダリを token として表現する
  webReq.ContentType = "multipart/mixed; boundary=" + boundary;

  var mem = new MemoryStream();
  var writer = new StreamWriter(mem, Encoding.UTF8);

  foreach (var s in files)
  {
    // 最初のバウンダリであっても、\r\n を先導させないと失敗する
    writer.Write("\r\n--" + boundary + "\r\n");

    // ここは「name="A"」でも「name=A」でも構わない
    writer.Write("Content-Disposition: mixed; name=" + Path.GetFileName(s) + "\r\n");
    //writer.Write("Content-Disposition: mixed; name=\"" + Path.GetFileName(s) + "\"\r\n");

    // Content-Type はもちろん必須だが、chaset の検査はされていない模様
    // というか、そもそも "text/csv" 以外を指定しても動いてしまう
    writer.Write("Content-Type: text/csv\r\n");

    // CSV の文字コードは UTF-8 にしておく必要がある模様
    // 使用可能な文字種(文字集合)は CP932 準拠?
    writer.Write("\r\n" + File.ReadAllText(s));
  }
  writer.Write("\r\n--" + boundary + "--");
  writer.Flush();
  webReq.ContentLength = mem.Length;
  mem.Position = 0;
  var reqStm = webReq.GetRequestStream();
  reqStm.Write(mem.ToArray(), 0, (int)mem.Length);
  reqStm.Close();
  writer.Dispose();

  var res = webReq.GetResponse();
  var resStream = new StreamReader(res.GetResponseStream(), Encoding.UTF8);
  string result = resStream.ReadToEnd();
  resStream.Close();
  res.Dispose();
  Console.WriteLine(result);
}

引用返信 編集キー/
■86383 / inTopicNo.10)  Re[5]: HttpClientでのmultipart/mixed送信
□投稿者/ さえ (4回)-(2018/01/25(Thu) 00:37:59)
2018/01/25(Thu) 00:39:57 編集(投稿者)
2018/01/25(Thu) 00:39:44 編集(投稿者)

魔界の仮面弁士様

ご丁寧にご指摘頂きありがとうございます。
No86325で書いて頂いていたこと、理解できておらずすみませんでした。

コードもどうもありがとうございます。無事実行することができました!

私の環境では、なぜかcharset=utf-8を指定しないと失敗し、
また、StreamWriterのコンストラクタにutf-8を指定するとBOMがついてしまい、しばらく悩みましたが。。

今回は本当に色々と勉強になりました。
皆様、どうもありがとうございました!




解決済み
引用返信 編集キー/

このトピックをツリーで一括表示


トピック内ページ移動 / << 0 >>

このトピックに書きこむ