読者です 読者をやめる 読者になる 読者になる

golangのparseライブラリで別サーバに向ける方法

我らがParse.comさんが本格的にサービス終了されました。それにより前に作ったinstall数集計golangアプリが動作しなくなりました。

pgmot.hatenablog.com

今回の環境ではOSSのparse-serverへの移行を成功させているので、回避策としては単純なもので、向き先サーバを変えるだけだったけどろくなドキュメントがなくて大変だったので書いときます。

結論

まず結論です。最初のParse Clientの作成部分は以下のようになっています。

// Client インストール数問い合わせのクライアント
type Client struct {
    parseClient parse.Client
}

// NewClient Client生成
func NewClient(applicationID string, restAPIKey string) *Client {
    return &Client{
        parseClient: parse.Client{
            Credentials: parse.RestAPIKey{
                ApplicationID: applicationID,
                RestAPIKey:    restAPIKey,
            },
        },
    }
}

client := parseclient.NewClient(defaultApplicationID, defaultRestAPIKey)

それをこうすれば他の実装を変えなくてもいいです。

// Client インストール数問い合わせのクライアント
type Client struct {
    parseClient parse.Client
}

// NewClient Client生成
func NewClient(applicationID string, restAPIKey string) *Client {
    return &Client{
        parseClient: parse.Client{
            Transport: &http.Transport{
                TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
            },
            Credentials: parse.RestAPIKey{
                ApplicationID: applicationID,
                RestAPIKey:    restAPIKey,
            },
            BaseURL: &url.URL{
                Scheme: "https",
                Host:   "parse.server.example.com",
                Path:   "",
            },
        },
    }
}

client := parseclient.NewClient(defaultApplicationID, defaultRestAPIKey)

Transport についてですが、今回建てたparse-sever側のSSL証明書をLet’s encryptにしていましてその証明書に対応しておらず認証失敗となるので、SSLの確認を回避することでとりあえずの回避となっています。これはちゃんとした鍵を置くとかすれば消してもいいでしょう(未検証)。

ソースレベルのお話

https://github.com/facebookgo/parse/blob/master/parse.go

アクセス先のサーバを決めているのは、この https://github.com/facebookgo/parse/blob/master/parse.go#L198 RoundTrip関数

func (c *Client) RoundTrip(req *http.Request) (*http.Response, error) {
    req.Proto = "HTTP/1.1"
    req.ProtoMajor = 1
    req.ProtoMinor = 1

    if req.URL == nil {
        if c.BaseURL == nil {
            req.URL = &defaultBaseURL
        } else {
            req.URL = c.BaseURL
        }
    } else {
        if !req.URL.IsAbs() {
            if c.BaseURL == nil {
                req.URL = defaultBaseURL.ResolveReference(req.URL)
            } else {
                req.URL = c.BaseURL.ResolveReference(req.URL)
            }
        }
    }

    if req.Host == "" {
        req.Host = req.URL.Host
    }

    if req.Header == nil {
        req.Header = make(http.Header)
    }

    var userAgent string
    if c.UserAgent == "" {
        userAgent = defaultUserAgent
    } else {
        userAgent = c.UserAgent
    }

    req.Header.Add(userAgentHeader, userAgent)
    if c.Credentials != nil {
        if err := c.Credentials.Modify(req); err != nil {
            return nil, err
        }
    }

    res, err := c.transport().RoundTrip(req)
    if err != nil {
        return res, err
    }

    if res.StatusCode > 399 || res.StatusCode < 200 {
        body, err := ioutil.ReadAll(res.Body)
        res.Body.Close()
        if err != nil {
            return res, err
        }

        if len(body) > 0 {
            var apiErr Error
            if json.Unmarshal(body, &apiErr) == nil {
                return res, &apiErr
            }
        }
        return res, &RawError{
            StatusCode: res.StatusCode,
            Body:       body,
        }
    }

    return res, nil
}

最初の部分だけ見れば良い

   if req.URL == nil {
        if c.BaseURL == nil {
            req.URL = &defaultBaseURL
        } else {
            req.URL = c.BaseURL
        }
    } else {
        if !req.URL.IsAbs() {
            if c.BaseURL == nil {
                req.URL = defaultBaseURL.ResolveReference(req.URL)
            } else {
                req.URL = c.BaseURL.ResolveReference(req.URL)
            }
        }
    }

req.URLGetPost などを呼び出す際に渡した http.Request なので基本的にはnilになるはず。 でcことclientのBaseURLの存在を見ています。

defaultBaseURL

   defaultBaseURL = url.URL{
        Scheme: "https",
        Host:   "api.parse.com",
        Path:   "/1/",
    }

こうなっているので同じような構造のものをクライアントに渡せば良いことがわかります。

まとめ

自前のparse-serverではlimitに制限がないのでガンガンAPI Callできていいですね。