質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Webサーバー

Webサーバーとは、HTTPリクエストに応じて、クライアントに情報を提供するシステムです。

Q&A

解決済

2回答

2292閲覧

WebアプリケーションのHTTPリクエストをPOSTで送信する際のパラメータの渡し方について

TOMO6181

総合スコア39

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Webサーバー

Webサーバーとは、HTTPリクエストに応じて、クライアントに情報を提供するシステムです。

0グッド

0クリップ

投稿2018/09/02 05:46

Webアプリケーションを使用したコードについて質問です。
クライアントからHTTPリクエストをPOSTで送信し、
サーバー側の処理で送信したHTTPリクエスト、及びパラメータの値を取り出し、
コンソール出力するプログラムを作成しています。
サーバー側のコードを以下に示します。

C#

1class Program 2{ 3 static void Main(string[] args) 4 { 5 bool flag = false; 6 7 while(true) 8 { 9 // 1回目に関数を呼び出してからは、DoServerのawaitでプログラムを終了しないようにする 10 if (!flag) 11 { 12 DoServer(); 13 flag = true; 14 } 15 } 16 } 17 18 /// <summary> 19 /// サーバーの動作をする。 20 /// </summary> 21 static async private void DoServer() 22 { 23 // TCP接続を待ち受けるためのソケット用意 24 TcpListener tcp = new TcpListener(IPAddress.Loopback, 1234); 25 // クライアントからの接続を待機する。 26 tcp.Start(); 27 28 // 適当に待つ 29 Console.WriteLine("クライアント接続待ち中…"); 30 31 while (true) 32 { 33 using (TcpClient client = await tcp.AcceptTcpClientAsync()) // クライアントからのインスタンスを受け取る 34 using (NetworkStream stream = client.GetStream()) // インスタンスからストリームを取り出す 35 using (StreamReader reader = new StreamReader(stream)) // ストリームから読み取り用ストリーム取得 36 using (StreamWriter writer = new StreamWriter(stream)) // ストリームから書き込み用ストリーム取得 37 { 38 // 接続先のクライアントを出力 39 Console.WriteLine(client.Client.RemoteEndPoint); 40 41 // 表示用の文字 42 string headerStr = string.Empty; 43 // 解釈用に、取得した文字列を全て取得する 44 List<string> readList = new List<string>(); 45 46 do 47 { 48 // 読み込みストリームから、文字を1行読み込む 49 headerStr = await reader.ReadLineAsync(); 50 51 if (headerStr != null) 52 { 53 // 読んだ行を出力 54 Console.WriteLine(headerStr); 55 56 // 解釈用のリストに入れる 57 readList.Add(headerStr); 58 } 59 60 } while (headerStr != null); // ReadLineAsyncは、最後まで行くとnullを返す 61 62 if (readList == null || readList.Count == 0) 63 { 64 // エラーのステータスが返っていることをレスポンスに帰す 65 await writer.WriteLineAsync("HTTP/1.0 400 Bad Request"); 66 await writer.WriteLineAsync("Content-Type: text/plain; charset=UTF-8"); 67 await writer.WriteLineAsync(); 68 await writer.WriteLineAsync("Bad Request"); 69 return; 70 } 71 72 // 1行目を読み取る(GET/POST部。パラメータも含んでいる。) 73 string readLine = readList[0]; 74 75 // 1行目はGET/HTTPを示しているので、半角スペース区切りで取得 76 string[] readLineAddr = readLine.Split(' '); 77 78 // 3つない場合はエラーである 79 if (readLineAddr == null || readLineAddr.Count() != 3) 80 { 81 // エラーのステータスが返っていることをレスポンスに帰す 82 await writer.WriteLineAsync("HTTP/1.0 400 Bad Request"); 83 await writer.WriteLineAsync("Content-Type: text/plain; charset=UTF-8"); 84 await writer.WriteLineAsync(); 85 await writer.WriteLineAsync("Bad Request"); 86 return; 87 } 88 89 // パラメータの文字列取得 90 Dictionary<string, string> paramDict = new Dictionary<string, string>(); 91 92 if (readLineAddr[0] == "GET") 93 { 94 // GETメソッド取得時のパラメータの文字列取得 95 paramDict = GetParameterStr(readLineAddr[1]); 96 } 97 else if (readLineAddr[0] == "POST") 98 { 99 // POSTメソッド取得時のパラメータの文字列取得 100 paramDict = PostParameterStr(readList); 101 } 102 else 103 { 104 // エラーのステータスが返っていることをレスポンスに帰す 105 await writer.WriteLineAsync("HTTP/1.0 400 Bad Request"); 106 await writer.WriteLineAsync("Content-Type: text/plain; charset=UTF-8"); 107 await writer.WriteLineAsync(); 108 await writer.WriteLineAsync("Bad Request"); 109 return; 110 } 111 112 if (paramDict != null && paramDict.Count > 0) 113 { 114 foreach (KeyValuePair<string, string> keyPair in paramDict) 115 { 116 // リクエストのKey値、Value値を出力する 117 Console.WriteLine(string.Format("Key:{0}, Value:{1}", keyPair.Key, keyPair.Value)); 118 } 119 120 Console.WriteLine(""); 121 } 122 123 // レスポンスを返す 124 // ヘッダー部 125 await writer.WriteLineAsync("HTTP/1.0 200 OK"); 126 await writer.WriteLineAsync("Content-Type: text/plain; charset=UTF-8"); 127 await writer.WriteLineAsync(); // 終わり 128 129 // ボディ 130 await writer.WriteLineAsync("Hello!World!"); 131 } 132 } 133 } 134 135 /// <summary> 136 /// 取得したGETリクエストからパラメータ部の文字列を取り出す。 137 /// </summary> 138 /// <param name="readLine"></param> 139 /// <returns></returns> 140 static private Dictionary<string, string> GetParameterStr(string paramStr) 141 { 142 // HTMLとパラメータは「?」区切り 143 string[] dataList = paramStr.Split('?'); 144 145 if (dataList == null || dataList.Count() < 2) 146 { 147 return new Dictionary<string, string>(); 148 } 149 150 // パラメータは「&」区切りなので各々を返す。これは、ない場合もある 151 string[] paramList = dataList[1].Split('&'); 152 153 if (paramList == null || paramList.Count() < 2) 154 { 155 // ない場合は空の値を返す 156 return new Dictionary<string, string>(); 157 } 158 159 // パラメータのDictionary 160 Dictionary<string, string> paramDict = new Dictionary<string, string>(); 161 162 foreach (string paramData in paramList) 163 { 164 // パラメータは「=」で結ばれている 165 string[] paramPair = paramData.Split('='); 166       167       if (paramPair.Count() == 2) 168 { 169 // パラメータのKey名とValue名を取得 170 paramDict.Add(paramPair[0], paramPair[1]); 171 } 172 } 173 174 return paramDict; 175 } 176 177 /// <summary> 178 /// 取得したPOSTリクエストからパラメータ部の文字列を取り出す 179 /// </summary> 180 /// <param name="readList"></param> 181 /// <returns></returns> 182 private static Dictionary<string, string> PostParameterStr(List<string> readList) 183 { 184 Dictionary<string, string> retDict = new Dictionary<string,string>(); 185 bool flag = false; 186 187 foreach (string readline in readList) 188 { 189 if (string.IsNullOrEmpty(readline)) 190 { 191 // 空文字があれば、その次がPOSTメソッドのパラメータ部である。 192 flag = true; 193 } 194 195 if(flag && !string.IsNullOrEmpty(readline)) 196 { 197 // POST部のパラメータのフラグONで、空文字でない場合、パラメータ取得 198 string[] paramList = readline.Split('&'); 199 200 foreach (string param in paramList) 201 { 202 // パラメータを取得 203 string[] data = param.Split('='); 204 205 if (data.Count() == 2) 206 { 207 // key = valueの形式のものだけ格納する 208 retDict.Add(data[0], data[1]); 209 } 210 } 211 } 212 } 213 214 return retDict; 215 } 216}

クライアント側のコードを以下に示します。

C#

1public partial class Form1 : Form 2{ 3 public Form1() 4 { 5 InitializeComponent(); 6 } 7 8 /// <summary> 9 /// 送信ボタン押下 10 /// </summary> 11 /// <param name="sender"></param> 12 /// <param name="e"></param> 13 private void btnSend_Click(object sender, EventArgs e) 14 { 15 const string url = "http://localhost:1234/index.html"; 16 // Webクライアントを設定 17 WebClient webclient = new WebClient(); 18 // 19 NameValueCollection nameValue = new NameValueCollection(); 20 21 nameValue.Add("word", "Internet"); 22 nameValue.Add("id", "1"); 23 24 // 指定したURLにデータを送信 25 byte[] resData = webclient.UploadValues(url, nameValue); 26 27 webclient.Dispose(); 28 29 // レスポンス表示 30 string resText = Encoding.UTF8.GetString(resData); 31 this.txtRedData.Text = resText; 32 } 33}

最初にサーバー側の処理を実行し、次にクライアント側の処理を実行すると、
コンソール出力には以下のように表示されます。

POST /index.html HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: localhost:1234
Content-Length: 18
Expect: 100-continue
Connection: Keep-Alive

本来であれば、

POST /index.html HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: localhost:1234
Content-Length: 18
Expect: 100-continue
Connection: Keep-Alive
<空行>
word=Internet&id=1

のように表示され、「Connection」の2行下にパラメータ部である
「word=Internet&id=1」
を表示したいのですが、サーバー側のDoServerメソッドで、空行が来た次のループで
「ReadLineAsync」を実行するとDoServerメソッドを抜けてしまっているようです。
改善点、修正点が分かる方がおられましたらお教え下さい。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

サーバー側のコードを以下のように修正しました。

C#

1 2namespace WebAppServer 3{ 4 class Program 5 { 6 /// <summary> 7 /// サーバーの動作をする。 8 /// </summary> 9 static async private void DoServer() 10 { 11 // TCP接続を待ち受けるためのソケット用意 12 TcpListener tcp = new TcpListener(IPAddress.Loopback, 1234); 13 // クライアントからの接続を待機する。 14 tcp.Start(); 15 16 // 適当に待つ 17 Console.WriteLine("クライアント接続待ち中…"); 18 19 while (true) 20 { 21 using (TcpClient client = await tcp.AcceptTcpClientAsync()) // クライアントからのインスタンスを受け取る 22 using (NetworkStream stream = client.GetStream()) // インスタンスからストリームを取り出す 23 using (StreamReader reader = new StreamReader(stream)) // ストリームから読み取り用ストリーム取得 24 using (StreamWriter writer = new StreamWriter(stream)) // ストリームから書き込み用ストリーム取得 25 { 26 // 接続先のクライアントを出力 27 Console.WriteLine(client.Client.RemoteEndPoint); 28 29 // ストリーム読み取り用のバッファ定義 30 char[] buffer = new char[256]; 31 // ストリームから読み取った文字列長 32 int length = 0; 33 // 受信データ格納用文字列 34 string rcvData = string.Empty; 35 36 do 37 { 38 // 読み取り可能な文字が存在する場合 39 if (client.Available > 0) 40 { 41 // バッファ配列クリア 42 Array.Clear(buffer, 0, buffer.Length); 43 44 // ストリームから読み取り 45 // Readを行うとストリームから読み取り可能な文字数が減るため、client.Availableの値が更新される 46 length = reader.Read(buffer, 0, buffer.Length); 47 48 // 受信した文字をstring型に変換 49 string data = new string(buffer); 50 rcvData += data; 51 } 52 } while (length > 0 && buffer[length - 1] != '\0' && client.Available > 0); // 末尾が空文字、または読み取り可能文字が0になるまで続ける 53 54 55 // 改行文字区切りにする 56 // 区切り文字列定義 57 string[] del = { "\r\n" }; 58 string[] strArray = rcvData.TrimEnd('\0').Split(del, StringSplitOptions.None); 59 60 foreach (string str in strArray) 61 { 62 // 読んだ行を出力 63 Console.WriteLine(str); 64 } 65 66 if (strArray == null || strArray.Count() == 0) 67 { 68 // エラーのステータスが返っていることをレスポンスに帰す 69 await writer.WriteLineAsync("HTTP/1.0 400 Bad Request"); 70 await writer.WriteLineAsync("Content-Type: text/plain; charset=UTF-8"); 71 await writer.WriteLineAsync(); 72 await writer.WriteLineAsync("Bad Request"); 73 return; 74 } 75 76 // 1行目を読み取る(GET/POST部。パラメータも含んでいる。) 77 string readLine = strArray[0]; 78 79 // 1行目はGET/HTTPを示しているので、半角スペース区切りで取得 80 string[] readLineArray = readLine.Split(' '); 81 82 // 3つない場合はエラーである 83 if (readLineArray == null || readLineArray.Count() != 3) 84 { 85 // エラーのステータスが返っていることをレスポンスに帰す 86 await writer.WriteLineAsync("HTTP/1.0 400 Bad Request"); 87 await writer.WriteLineAsync("Content-Type: text/plain; charset=UTF-8"); 88 await writer.WriteLineAsync(); 89 await writer.WriteLineAsync("Bad Request"); 90 return; 91 } 92 93 // パラメータの文字列取得 94 Dictionary<string, string> paramDict = new Dictionary<string, string>(); 95 96 if (readLineArray[0] == "GET") 97 { 98 // GETメソッド取得時のパラメータの文字列取得 99 paramDict = GetParameterStr(readLineArray[1]); 100 } 101 else if (readLineArray[0] == "POST") 102 { 103 // POSTメソッド取得時のパラメータの文字列取得 104 paramDict = PostParameterStr(strArray); 105 } 106 else 107 { 108 // エラーのステータスが返っていることをレスポンスに帰す 109 await writer.WriteLineAsync("HTTP/1.0 400 Bad Request"); 110 await writer.WriteLineAsync("Content-Type: text/plain; charset=UTF-8"); 111 await writer.WriteLineAsync(); 112 await writer.WriteLineAsync("Bad Request"); 113 return; 114 } 115 116 if (paramDict != null && paramDict.Count > 0) 117 { 118 foreach (KeyValuePair<string, string> keyPair in paramDict) 119 { 120 // リクエストのKey値、Value値を出力する 121 Console.WriteLine(string.Format("Key:{0}, Value:{1}", keyPair.Key, keyPair.Value)); 122 } 123 124 Console.WriteLine(""); 125 } 126 127 // レスポンスを返す 128 // ヘッダー部 129 await writer.WriteLineAsync("HTTP/1.0 200 OK"); 130 await writer.WriteLineAsync("Content-Type: text/plain; charset=UTF-8"); 131 await writer.WriteLineAsync(); // 終わり 132 133 // ボディ 134 await writer.WriteLineAsync("Hello!World!"); 135 } 136 } 137 } 138 139 /// <summary> 140 /// 取得したGETリクエストからパラメータ部の文字列を取り出す。 141 /// </summary> 142 /// <param name="readLine"></param> 143 /// <returns></returns> 144 static private Dictionary<string, string> GetParameterStr(string paramStr) 145 { 146 // HTMLとパラメータは「?」区切り 147 string[] dataList = paramStr.Split('?'); 148 149 if (dataList == null || dataList.Count() < 2) 150 { 151 return new Dictionary<string, string>(); 152 } 153 154 // パラメータは「&」区切りなので各々を返す。これは、ない場合もある 155 string[] paramList = dataList[1].Split('&'); 156 157 if (paramList == null || paramList.Count() < 2) 158 { 159 // ない場合は空の数値を返す 160 return new Dictionary<string, string>(); 161 } 162 163 // パラメータのDictionary 164 Dictionary<string, string> paramDict = new Dictionary<string, string>(); 165 166 foreach (string paramData in paramList) 167 { 168 // パラメータは「=」で結ばれている 169 string[] paramPair = paramData.Split('='); 170 171 // パラメータのKey名とValue名を取得 172 paramDict.Add(paramPair[0], paramPair[1]); 173 } 174 175 return paramDict; 176 } 177 178 /// <summary> 179 /// 取得したPOSTリクエストからパラメータ部の文字列を取り出す 180 /// </summary> 181 /// <param name="readArray"></param> 182 /// <returns></returns> 183 private static Dictionary<string, string> PostParameterStr(string[] readArray) 184 { 185 Dictionary<string, string> retDict = new Dictionary<string,string>(); 186 bool flag = false; 187 188 foreach (string readline in readArray) 189 { 190 if (string.IsNullOrEmpty(readline)) 191 { 192 // 空文字があれば、その次がPOSTメソッドのパラメータ部である。 193 flag = true; 194 } 195 196 if(flag && !string.IsNullOrEmpty(readline)) 197 { 198 // POST部のパラメータのフラグONで、空文字でない場合、パラメータ取得 199 string[] paramList = readline.Split('&'); 200 201 foreach (string param in paramList) 202 { 203 // パラメータを取得 204 string[] data = param.Split('='); 205 206 if (data.Count() == 2) 207 { 208 // key = valueの形式のものだけ格納する 209 retDict.Add(data[0], data[1]); 210 } 211 } 212 } 213 } 214 215 return retDict; 216 } 217 } 218} 219

ReadLineAsyncメソッドを使用していた個所をReadメソッドに修正しています。
TcpClientクラスのAvailableプロパティを見て、読み取り可能な文字数が0になるまでReadメソッドで受信した文字を取得するようにしています。

投稿2018/09/09 05:39

TOMO6181

総合スコア39

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

「ReadLineAsync」を実行するとDoServerメソッドを抜けてしまっているようです。

抜けているのではなく、ReadLineAsyncで、POSTのコンテントボディ部受信の時に最後の改行を待ってしまっています。

以下は、ご提示のWindows Formアプリケーションのクライアントから送出したHTTPリクエストと、そのリクエストをバイナリ16進数でダンプしたものです。(Windows 7 + Visual Studio 2017 + .NET Framework 4.6.1の環境で採取したデータです)

plain

1$ cat res1.txt 2POST /index.html HTTP/1.1 3Content-Type: application/x-www-form-urlencoded 4Host: localhost:1234 5Content-Length: 18 6Expect: 100-continue 7Connection: Keep-Alive 8 9word=Internet&id=1 10 11$ od -t xC res1.txt 120000000 50 4f 53 54 20 2f 69 6e 64 65 78 2e 68 74 6d 6c 130000020 20 48 54 54 50 2f 31 2e 31 0d 0a 43 6f 6e 74 65 140000040 6e 74 2d 54 79 70 65 3a 20 61 70 70 6c 69 63 61 150000060 74 69 6f 6e 2f 78 2d 77 77 77 2d 66 6f 72 6d 2d 160000100 75 72 6c 65 6e 63 6f 64 65 64 0d 0a 48 6f 73 74 170000120 3a 20 6c 6f 63 61 6c 68 6f 73 74 3a 31 32 33 34 180000140 0d 0a 43 6f 6e 74 65 6e 74 2d 4c 65 6e 67 74 68 190000160 3a 20 31 38 0d 0a 45 78 70 65 63 74 3a 20 31 30 200000200 30 2d 63 6f 6e 74 69 6e 75 65 0d 0a 43 6f 6e 6e 210000220 65 63 74 69 6f 6e 3a 20 4b 65 65 70 2d 41 6c 69 220000240 76 65 0d 0a 0d 0a 77 6f 72 64 3d 49 6e 74 65 72 230000260 6e 65 74 26 69 64 3d 31 240000270

コンテントボディ部はヘッダとの区切り、空行(0Dh+0Ah)を挟んでのword=Internet&id=1の部分ですが、この文字列の末尾は改行では終わっていません。ヘッダーではContent-Length: 18となっているので、18文字で正しいです。クライアント側に問題はありません。

ヘッダー部にContent-Length:フィールドがある限り、コンテントボディのデータ長はContent-Length:フィールドで記述されているバイト長です。データの末尾に改行を期待するStreamReader.ReadLine系のメソッドを使うべきではありませんし、改行で終わる保証はありません。ヘッダー部のContent-Length: で示されるデータ長分を読み出し、HTTPレスポンスを返すようにサーバー側を実装する必要があります。

投稿2018/09/07 01:45

編集2018/09/07 01:52
dodox86

総合スコア9183

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

TOMO6181

2018/09/09 05:26 編集

ご回答ありがとうございます。 ReadLineAsyncメソッドの改行待ちは盲点でした。 TcpClientクラスのAvailableプロパティを見て、読み取り可能な文字があるまでReadメソッドで読み込むように修正したところ、コンテントボディ部も取得することができました。 参考までに修正したソースコードを記載しています。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問