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

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

ただいまの
回答率

90.04%

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

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 1,847

TOMO6181

score 27

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

class Program
{
    static void Main(string[] args)
    {
        bool flag = false;

        while(true)
        {
            // 1回目に関数を呼び出してからは、DoServerのawaitでプログラムを終了しないようにする
            if (!flag)
            {
                DoServer();
                flag = true;
            }
        }
    }

    /// <summary>
    /// サーバーの動作をする。
    /// </summary>
    static async private void DoServer()
    {
        // TCP接続を待ち受けるためのソケット用意
        TcpListener tcp = new TcpListener(IPAddress.Loopback, 1234);
        // クライアントからの接続を待機する。
        tcp.Start();

        // 適当に待つ
        Console.WriteLine("クライアント接続待ち中…");

        while (true)
        {
            using (TcpClient client = await tcp.AcceptTcpClientAsync()) // クライアントからのインスタンスを受け取る
            using (NetworkStream stream = client.GetStream())   // インスタンスからストリームを取り出す
            using (StreamReader reader = new StreamReader(stream))    // ストリームから読み取り用ストリーム取得
            using (StreamWriter writer = new StreamWriter(stream))    // ストリームから書き込み用ストリーム取得
            {
                // 接続先のクライアントを出力
                Console.WriteLine(client.Client.RemoteEndPoint);

                // 表示用の文字
                string headerStr = string.Empty;
                // 解釈用に、取得した文字列を全て取得する
                List<string> readList = new List<string>();

                do
                {
                    // 読み込みストリームから、文字を1行読み込む
                    headerStr = await reader.ReadLineAsync();

                    if (headerStr != null)
                    {
                        // 読んだ行を出力
                        Console.WriteLine(headerStr);

                        // 解釈用のリストに入れる
                        readList.Add(headerStr);
                    }

                } while (headerStr != null);    // ReadLineAsyncは、最後まで行くとnullを返す

                if (readList == null || readList.Count == 0)
                {
                    // エラーのステータスが返っていることをレスポンスに帰す
                    await writer.WriteLineAsync("HTTP/1.0 400 Bad Request");
                    await writer.WriteLineAsync("Content-Type: text/plain; charset=UTF-8");
                    await writer.WriteLineAsync();
                    await writer.WriteLineAsync("Bad Request");
                    return;
                }

                // 1行目を読み取る(GET/POST部。パラメータも含んでいる。)
                string readLine = readList[0];

                // 1行目はGET/HTTPを示しているので、半角スペース区切りで取得
                string[] readLineAddr = readLine.Split(' ');

                // 3つない場合はエラーである
                if (readLineAddr == null || readLineAddr.Count() != 3)
                {
                    // エラーのステータスが返っていることをレスポンスに帰す
                    await writer.WriteLineAsync("HTTP/1.0 400 Bad Request");
                    await writer.WriteLineAsync("Content-Type: text/plain; charset=UTF-8");
                    await writer.WriteLineAsync();
                    await writer.WriteLineAsync("Bad Request");
                    return;
                }

                // パラメータの文字列取得
                Dictionary<string, string> paramDict = new Dictionary<string, string>();

                if (readLineAddr[0] == "GET")
                {
                    // GETメソッド取得時のパラメータの文字列取得
                    paramDict = GetParameterStr(readLineAddr[1]);
                }
                else if (readLineAddr[0] == "POST")
                {
                    // POSTメソッド取得時のパラメータの文字列取得
                    paramDict = PostParameterStr(readList);
                }
                else
                {
                    // エラーのステータスが返っていることをレスポンスに帰す
                    await writer.WriteLineAsync("HTTP/1.0 400 Bad Request");
                    await writer.WriteLineAsync("Content-Type: text/plain; charset=UTF-8");
                    await writer.WriteLineAsync();
                    await writer.WriteLineAsync("Bad Request");
                    return;
                }

                if (paramDict != null && paramDict.Count > 0)
                {
                    foreach (KeyValuePair<string, string> keyPair in paramDict)
                    {
                        // リクエストのKey値、Value値を出力する
                        Console.WriteLine(string.Format("Key:{0}, Value:{1}", keyPair.Key, keyPair.Value));
                    }

                    Console.WriteLine("");
                }

                // レスポンスを返す
                // ヘッダー部
                await writer.WriteLineAsync("HTTP/1.0 200 OK");
                await writer.WriteLineAsync("Content-Type: text/plain; charset=UTF-8");
                await writer.WriteLineAsync(); // 終わり

                // ボディ
                await writer.WriteLineAsync("Hello!World!");
            }
        }
    }

    /// <summary>
    /// 取得したGETリクエストからパラメータ部の文字列を取り出す。
    /// </summary>
    /// <param name="readLine"></param>
    /// <returns></returns>
    static private Dictionary<string, string> GetParameterStr(string paramStr)
    {
        // HTMLとパラメータは「?」区切り
        string[] dataList = paramStr.Split('?');

        if (dataList == null || dataList.Count() < 2)
        {
            return new Dictionary<string, string>();
        }

        // パラメータは「&」区切りなので各々を返す。これは、ない場合もある
        string[] paramList = dataList[1].Split('&');

        if (paramList == null || paramList.Count() < 2)
        {
            // ない場合は空の値を返す
            return new Dictionary<string, string>();
        }

        // パラメータのDictionary
        Dictionary<string, string> paramDict = new Dictionary<string, string>();   

        foreach (string paramData in paramList)
        {
            // パラメータは「=」で結ばれている
            string[] paramPair = paramData.Split('=');
      
       if (paramPair.Count() == 2)
            {
                 // パラメータのKey名とValue名を取得
                 paramDict.Add(paramPair[0], paramPair[1]);
            }
        }

        return paramDict;
    }

    /// <summary>
    /// 取得したPOSTリクエストからパラメータ部の文字列を取り出す
    /// </summary>
    /// <param name="readList"></param>
    /// <returns></returns>
    private static Dictionary<string, string> PostParameterStr(List<string> readList)
    {
        Dictionary<string, string> retDict = new Dictionary<string,string>();
        bool flag = false;

        foreach (string readline in readList)
        {
            if (string.IsNullOrEmpty(readline))
            {
                // 空文字があれば、その次がPOSTメソッドのパラメータ部である。
                flag = true;
            }

            if(flag && !string.IsNullOrEmpty(readline))
            {
                // POST部のパラメータのフラグONで、空文字でない場合、パラメータ取得
                string[] paramList = readline.Split('&');

                foreach (string param in paramList)
                {
                    // パラメータを取得
                    string[] data = param.Split('=');

                    if (data.Count() == 2)
                    {
                        // key = valueの形式のものだけ格納する
                        retDict.Add(data[0], data[1]);
                    }
                }
            }
        }

        return retDict;
    }
}

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

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    /// <summary>
    /// 送信ボタン押下
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void btnSend_Click(object sender, EventArgs e)
    {
        const string url = "http://localhost:1234/index.html";
        // Webクライアントを設定
        WebClient webclient = new WebClient();
        // 
        NameValueCollection nameValue = new NameValueCollection();

        nameValue.Add("word", "Internet");
        nameValue.Add("id", "1");

        // 指定したURLにデータを送信
        byte[] resData = webclient.UploadValues(url, nameValue);

        webclient.Dispose();

        // レスポンス表示
        string resText = Encoding.UTF8.GetString(resData);
        this.txtRedData.Text = resText;
    }
}

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

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メソッドを抜けてしまっているようです。
改善点、修正点が分かる方がおられましたらお教え下さい。

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

0

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

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

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

$ cat res1.txt
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

$ od -t xC res1.txt
0000000 50 4f 53 54 20 2f 69 6e 64 65 78 2e 68 74 6d 6c
0000020 20 48 54 54 50 2f 31 2e 31 0d 0a 43 6f 6e 74 65
0000040 6e 74 2d 54 79 70 65 3a 20 61 70 70 6c 69 63 61
0000060 74 69 6f 6e 2f 78 2d 77 77 77 2d 66 6f 72 6d 2d
0000100 75 72 6c 65 6e 63 6f 64 65 64 0d 0a 48 6f 73 74
0000120 3a 20 6c 6f 63 61 6c 68 6f 73 74 3a 31 32 33 34
0000140 0d 0a 43 6f 6e 74 65 6e 74 2d 4c 65 6e 67 74 68
0000160 3a 20 31 38 0d 0a 45 78 70 65 63 74 3a 20 31 30
0000200 30 2d 63 6f 6e 74 69 6e 75 65 0d 0a 43 6f 6e 6e
0000220 65 63 74 69 6f 6e 3a 20 4b 65 65 70 2d 41 6c 69
0000240 76 65 0d 0a 0d 0a 77 6f 72 64 3d 49 6e 74 65 72
0000260 6e 65 74 26 69 64 3d 31
0000270


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

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/09/09 14:17 編集

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

    キャンセル

0

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

namespace WebAppServer
{
    class Program
    {
        /// <summary>
        /// サーバーの動作をする。
        /// </summary>
        static async private void DoServer()
        {
            // TCP接続を待ち受けるためのソケット用意
            TcpListener tcp = new TcpListener(IPAddress.Loopback, 1234);
            // クライアントからの接続を待機する。
            tcp.Start();

            // 適当に待つ
            Console.WriteLine("クライアント接続待ち中…");

            while (true)
            {
                using (TcpClient client = await tcp.AcceptTcpClientAsync()) // クライアントからのインスタンスを受け取る
                using (NetworkStream stream = client.GetStream())   // インスタンスからストリームを取り出す
                using (StreamReader reader = new StreamReader(stream))    // ストリームから読み取り用ストリーム取得
                using (StreamWriter writer = new StreamWriter(stream))    // ストリームから書き込み用ストリーム取得
                {
                    // 接続先のクライアントを出力
                    Console.WriteLine(client.Client.RemoteEndPoint);

                    // ストリーム読み取り用のバッファ定義
                    char[] buffer = new char[256];
                    // ストリームから読み取った文字列長
                    int length = 0;
                    // 受信データ格納用文字列
                    string rcvData = string.Empty;

                    do
                    {
                        // 読み取り可能な文字が存在する場合
                        if (client.Available > 0)
                        {
                            // バッファ配列クリア
                            Array.Clear(buffer, 0, buffer.Length);

                            // ストリームから読み取り
                            // Readを行うとストリームから読み取り可能な文字数が減るため、client.Availableの値が更新される
                            length = reader.Read(buffer, 0, buffer.Length);

                            // 受信した文字をstring型に変換
                            string data = new string(buffer);
                            rcvData += data;
                        }
                    } while (length > 0 && buffer[length - 1] != '\0' && client.Available > 0); // 末尾が空文字、または読み取り可能文字が0になるまで続ける


                    // 改行文字区切りにする
                    // 区切り文字列定義
                    string[] del = { "\r\n" };
                    string[] strArray = rcvData.TrimEnd('\0').Split(del, StringSplitOptions.None);

                    foreach (string str in strArray)
                    {
                        // 読んだ行を出力
                        Console.WriteLine(str);
                    }

                    if (strArray == null || strArray.Count() == 0)
                    {
                        // エラーのステータスが返っていることをレスポンスに帰す
                        await writer.WriteLineAsync("HTTP/1.0 400 Bad Request");
                        await writer.WriteLineAsync("Content-Type: text/plain; charset=UTF-8");
                        await writer.WriteLineAsync();
                        await writer.WriteLineAsync("Bad Request");
                        return;
                    }

                    // 1行目を読み取る(GET/POST部。パラメータも含んでいる。)
                    string readLine = strArray[0];

                    // 1行目はGET/HTTPを示しているので、半角スペース区切りで取得
                    string[] readLineArray = readLine.Split(' ');

                    // 3つない場合はエラーである
                    if (readLineArray == null || readLineArray.Count() != 3)
                    {
                        // エラーのステータスが返っていることをレスポンスに帰す
                        await writer.WriteLineAsync("HTTP/1.0 400 Bad Request");
                        await writer.WriteLineAsync("Content-Type: text/plain; charset=UTF-8");
                        await writer.WriteLineAsync();
                        await writer.WriteLineAsync("Bad Request");
                        return;
                    }

                    // パラメータの文字列取得
                    Dictionary<string, string> paramDict = new Dictionary<string, string>();

                    if (readLineArray[0] == "GET")
                    {
                        // GETメソッド取得時のパラメータの文字列取得
                        paramDict = GetParameterStr(readLineArray[1]);
                    }
                    else if (readLineArray[0] == "POST")
                    {
                        // POSTメソッド取得時のパラメータの文字列取得
                        paramDict = PostParameterStr(strArray);
                    }
                    else
                    {
                        // エラーのステータスが返っていることをレスポンスに帰す
                        await writer.WriteLineAsync("HTTP/1.0 400 Bad Request");
                        await writer.WriteLineAsync("Content-Type: text/plain; charset=UTF-8");
                        await writer.WriteLineAsync();
                        await writer.WriteLineAsync("Bad Request");
                        return;
                    }

                    if (paramDict != null && paramDict.Count > 0)
                    {
                        foreach (KeyValuePair<string, string> keyPair in paramDict)
                        {
                            // リクエストのKey値、Value値を出力する
                            Console.WriteLine(string.Format("Key:{0}, Value:{1}", keyPair.Key, keyPair.Value));
                        }

                        Console.WriteLine("");
                    }

                    // レスポンスを返す
                    // ヘッダー部
                    await writer.WriteLineAsync("HTTP/1.0 200 OK");
                    await writer.WriteLineAsync("Content-Type: text/plain; charset=UTF-8");
                    await writer.WriteLineAsync(); // 終わり

                    // ボディ
                    await writer.WriteLineAsync("Hello!World!");
                }
            }
        }

        /// <summary>
        /// 取得したGETリクエストからパラメータ部の文字列を取り出す。
        /// </summary>
        /// <param name="readLine"></param>
        /// <returns></returns>
        static private Dictionary<string, string> GetParameterStr(string paramStr)
        {
            // HTMLとパラメータは「?」区切り
            string[] dataList = paramStr.Split('?');

            if (dataList == null || dataList.Count() < 2)
            {
                return new Dictionary<string, string>();
            }

            // パラメータは「&」区切りなので各々を返す。これは、ない場合もある
            string[] paramList = dataList[1].Split('&');

            if (paramList == null || paramList.Count() < 2)
            {
                // ない場合は空の数値を返す
                return new Dictionary<string, string>();
            }

            // パラメータのDictionary
            Dictionary<string, string> paramDict = new Dictionary<string, string>();   

            foreach (string paramData in paramList)
            {
                // パラメータは「=」で結ばれている
                string[] paramPair = paramData.Split('=');

                // パラメータのKey名とValue名を取得
                paramDict.Add(paramPair[0], paramPair[1]);
            }

            return paramDict;
        }

        /// <summary>
        /// 取得したPOSTリクエストからパラメータ部の文字列を取り出す
        /// </summary>
        /// <param name="readArray"></param>
        /// <returns></returns>
        private static Dictionary<string, string> PostParameterStr(string[] readArray)
        {
            Dictionary<string, string> retDict = new Dictionary<string,string>();
            bool flag = false;

            foreach (string readline in readArray)
            {
                if (string.IsNullOrEmpty(readline))
                {
                    // 空文字があれば、その次がPOSTメソッドのパラメータ部である。
                    flag = true;
                }

                if(flag && !string.IsNullOrEmpty(readline))
                {
                    // POST部のパラメータのフラグONで、空文字でない場合、パラメータ取得
                    string[] paramList = readline.Split('&');

                    foreach (string param in paramList)
                    {
                        // パラメータを取得
                        string[] data = param.Split('=');

                        if (data.Count() == 2)
                        {
                            // key = valueの形式のものだけ格納する
                            retDict.Add(data[0], data[1]);
                        }
                    }
                }
            }

            return retDict;
        }
    }
}


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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

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

  • ただいまの回答率 90.04%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる
  • トップ
  • C#に関する質問
  • WebアプリケーションのHTTPリクエストをPOSTで送信する際のパラメータの渡し方について