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

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

ただいまの
回答率

90.53%

  • Go

    624questions

    Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

  • HttpWebRequest

    65questions

    HttpWebRequestとは.NETにおけるクラスであり、WebRequestクラスをHTTPに導入するものです。

`http.Client.Do()`メソッドを使う際、既存のリクエストを再利用する方法を教えてください。

解決済

回答 2

投稿 編集

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

jw_1029

score 10

前提・実現したいこと

Go言語のGorillaを使ってセッションを管理しています。
クライアント側からサーバ側に新しいリクエストを生成して送信した場合、前回保存したセッション情報を読み込むことができませんでした。
この場合、セッションの維持する方法を教えてください。

発生している問題・エラーメッセージ

エラーメッセージは特にありません。

該当のソースコード

// Client側
package main

import (
    "io/ioutil"
    "log"
    "net/http"

    "github.com/gorilla/mux"
    "github.com/gorilla/securecookie"
    "github.com/gorilla/sessions"
)

var (
    store       *sessions.CookieStore = sessions.NewCookieStore(securecookie.GenerateRandomKey(64))
    session     *sessions.Session
    SessionName = "client"
)

func main() {
    store.Options = &sessions.Options{
        Path:     "/",
        MaxAge:   60 * 15,
        Secure:   false,
        HttpOnly: true,
    }
    router := mux.NewRouter()
    router.HandleFunc("/", cIndex)
    router.HandleFunc("/test", cTest)
    http.ListenAndServe(":7000", router)
}

func cIndex(w http.ResponseWriter, r *http.Request) {
    session, err := store.Get(r, SessionName)
    if err != nil {
        log.Println("Client Error 1 : ", err.Error())
    }
    session.Values["foo"] = "bar"
    session.Save(r, w)
    w.Header().Set("Location", "http://localhost:8080?foo=bar")
    w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
    w.WriteHeader(http.StatusFound)

}

func cTest(w http.ResponseWriter, r *http.Request) {
    req, err := http.NewRequest("GET", "http://localhost:8080/test", nil)
    if err != nil {
        log.Println("Client Error 2 : ", err.Error())
    }
    req.SetBasicAuth("sample_id", "sample_secret")
    // req.Header.Set("Location", provider.TokenEP+"?"+t.encode())
    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    c := &http.Client{
        Transport: &http.Transport{},
    }
    resp, err := c.Do(req)
    if err != nil {
        log.Println("Client Error 3 : ", err.Error())
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        log.Println("Client Error 4 : ", err.Error())
    }
    log.Println(string(body))
}
// サーバ側
package main

import (
    "encoding/json"
    "log"
    "net/http"

    "github.com/gorilla/mux"
    "github.com/gorilla/securecookie"
    "github.com/gorilla/sessions"
)

var (
    store       *sessions.CookieStore = sessions.NewCookieStore(securecookie.GenerateRandomKey(64))
    session     *sessions.Session
    SessionName = "server"
)

func main() {
    store.Options = &sessions.Options{
        Path:     "/",
        MaxAge:   60 * 15,
        Secure:   false,
        HttpOnly: true,
    }
    router := mux.NewRouter()
    router.HandleFunc("/", sIndex)
    router.HandleFunc("/test", sTest)
    http.ListenAndServe(":8080", router)
}

func sIndex(w http.ResponseWriter, r *http.Request) {
    r.ParseForm()
    v := r.FormValue("foo")
    session, err := store.Get(r, SessionName)
    if err != nil {
        log.Println("Server Error 1 : ", err.Error())
    }
    session.Values["foo"] = v
    session.Save(r, w)
    w.Header().Set("Location", "http://localhost:7000/test")
    w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
    w.WriteHeader(http.StatusFound)
}

func sTest(w http.ResponseWriter, r *http.Request) {
    session, err := store.Get(r, SessionName)
    if err != nil {
        log.Println("Server Error 2 : ", err.Error())
    }
    v, ok := session.Values["foo"].(string)
    if !ok {
        log.Printf("foo = %v\n", v)
    }
    data := struct {
        Foo string
    }{
        v, // I want v is bar
    }

    bytes, err := json.Marshal(data)
    if err != nil {
        log.Println("Server Error 3 : ", err.Error())
    }
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    w.Write(bytes)
}

試したこと

① クライアント側でhttp.Clientを生成する時、http.Transportも生成してリクエストを送信する。(c := &http.Client{Transport:&http.Transport{}})
② リクエスト生成後、コンテキストを使用してリクエスト送信する。(req = request.WithContext(context.BackgroundContext()))

補足情報(言語/FW/ツール等のバージョンなど)

20170704 追記
タイトル選択にミスがあり、修正しました。
私が聞きたかった質問はリクエストを再利用する方法でした。
試したコードで動作するのは確認しましたが、これよりいい方法があれば教えてください。

// Client
func cTest(w http.ResponseWrite, r *http.Request) {
    serverURL := "http://localhost:8080/test"
    r.Method = http.MethodGet
    r.Host = serverURL  // server
    v, err := url.Parse(serverURL)
    if err != nil {
        log.Println("url parsing error occurred : ", err.Error())
        return
    }
    c := &http.Client{Transport: &http.Transport{}}
    resp, err := c.Transport.RoundTrip(r)
    if err != nil {
        log.Println("client roundtrip error occurred : ", err.Error())
        return
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        log.Println("response body reading error occurred : ", err.Error())
        return
    }
    w.Write(body)
}
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 2

check解決した方法

0

下記の方法を使えば、サーバ側で以前設定したセッションを使うことが出来ました。

// この以前にClient・サーバの間データをやり取りして、サーバのセッションにデータを保存しておきました。
// Client
func cTest(w http.ResponseWriter, r *http.Request) {
    serverURL := "https://localhost:8080/test"
    r.Method = http.MethodGet
    r.Host = serverURL  // server
    v, err := url.Parse(serverURL)
    if err != nil {
        log.Println("url parsing error occurred : ", err.Error())
        return
    }
    r.URL = v
    c := &http.Client{ 
        // 開発環境では自己証明した証明書を使いますので、証明書を無視するコードが書いてあります。
        Transport: &http.Transport{
            TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
        },
    }
    resp, err := c.Transport.RoundTrip(r)
    if err != nil {
        log.Println("client roundtrip error occurred : ", err.Error())
        return
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        log.Println("response body reading error occurred : ", err.Error())
        return
    }
    w.Write(body)
}

しかし、mattnさんの説明を読んで、クライアント側でhttp.Clientを使いまわせば、より良い方法で同じ結果を得ることが出来るかと思います。

テストして追記します。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/07/12 23:50 編集

    Transport の差し替えはなぜ必要なのですか?もう少しいうとリクエストを再利用する理由が分かりません。

    キャンセル

  • 2017/07/13 00:02 編集

    コードが詳しく書いてなかったですね。
    すみません。
    私が書いている実際のコードではTLSを使っています。
    Transportに下の

    TLSClientConfig: &tls.Config{InsecureSkipVerify: true}

    コードを追加しないとエラーが発生するので、Transportを差し替えました。

    また、クライアント側からサーバにcURLのようにリクエストを送信したいと思っていて、その間セッションも維持したいと思ってリクエストを再利用しようと思いました。

    キャンセル

  • 2017/07/13 00:08 編集

    http.Request はステートを持ちません。なのでもともと再利用できるように設計されています。セッションを保持しているのはクッキー(サーバがクッキーセッションを使っておられる所から分かります)であって、「セッションを保持する」に対してやるべきは http.Client.Jar を使う事の様に思います。現状、質問に載せて頂いているコードではセッションは保持しない様に思えます。

    追記: あと、クッキーが保存されるのは http.Client であって Transport ではないので、http.Client を使いまわさない限り前回のセッションを引き継ぐ事は出来ません。

    さらに追記: しつこい様ですみません。何故ここまで言うのかというと Google 検索で「リクエストを再利用をする」等をキーワードにこの回答を見つけてきた人が「下の方法を参考にすればと思います」と書かれてしまっているのを信じてしまうのが困るからです。

    キャンセル

  • 2017/07/13 10:01

    mattnさん、詳細な説明ありがとうございます!
    最初からhttp.Clientを使いまわすように設計しないといけないのですね。
    クライアント側のコードを修正してみます。
    私は今までクライアント側もサーバのように作成しました。
    解決方法のところは修正します。

    キャンセル

0

サーバ側のコードでセッションクッキーを使っておられるので、当然ですがクライアントがクッキー対応している必要があります。以下の様に cookiejar を付けて http.Client を作成するとクッキー対応できます。
この client を使いまわせばセッションが有効になるかと思います。

package main

import (
    "log"
    "net/http"
    "net/http/cookiejar"
)

func main() {
    jar, err := cookiejar.New(nil)
    if err != nil {
        log.Fatal(err)
    }

    client := &http.Client{
        Jar: jar,
    }

    if _, err = client.Get("http://example.com/"); err != nil {
        log.Fatal(err)
    }
    // この後も client を使う
}

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/07/03 23:50

    mattnさん、ご答えありがとうございます。
    クライアント側で`cookiejar`をつけて試してみた結果、サーバ側が以前保存したセッションの値は読み込めなかったです。
    サーバ側で何か実装すべきのコードがありますでしょうか?

    キャンセル

  • 2017/07/03 23:52 編集

    client を使いまわしているでしょうか?作り直すとクッキーが破棄され新しいセッションとなってしまいます。

    キャンセル

  • 2017/07/04 09:54

    私が質問のタイトルをうまく書きませんでした。私が質問したかったのは`client.Do()`メソッドを使う時に既存のリクエストを使う方法がないかということでした。

    キャンセル

  • 2017/07/04 10:16

    http.Request#Body が ReadCloser なので POST データを再度入れなおせば使いまわせるかと思います。

    キャンセル

同じタグがついた質問を見る

  • Go

    624questions

    Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

  • HttpWebRequest

    65questions

    HttpWebRequestとは.NETにおけるクラスであり、WebRequestクラスをHTTPに導入するものです。

  • トップ
  • Goに関する質問
  • `http.Client.Do()`メソッドを使う際、既存のリクエストを再利用する方法を教えてください。