From ba00a7ba7f837dda388c5590129e10bc1a89528d Mon Sep 17 00:00:00 2001 From: Majid Karimizadeh Date: Sat, 9 Jul 2022 21:32:18 +0200 Subject: [PATCH] Supporting multiple instance of parse server --- common_test.go | 60 ++++++++++++++++++++++++++++++---------- create.go | 8 +++--- create_test.go | 47 +++++++++++++++++++++++++++++++ delete.go | 6 ++-- delete_test.go | 39 ++++++++++++++++++++++++++ function.go | 8 +++--- push.go | 8 +++--- query.go | 16 +++++------ query_test.go | 26 +++++++++++++++++ rest.go | 75 ++++++++++++++++++++++++++++++++++---------------- session.go | 16 +++++------ types.go | 15 ++++++---- update.go | 6 ++-- 13 files changed, 253 insertions(+), 77 deletions(-) diff --git a/common_test.go b/common_test.go index 089ccde..b613666 100644 --- a/common_test.go +++ b/common_test.go @@ -10,27 +10,49 @@ import ( ) type ctxT struct { - ts *httptest.Server - oldHost string - oldHttpClient *http.Client + ts1 *httptest.Server + oldHost1 string + oldHttpClient1 *http.Client + + ts2 *httptest.Server + oldHost2 string + oldHttpClient2 *http.Client } var ctx = ctxT{} -func setupTestServer(handler http.HandlerFunc) *httptest.Server { - ts := httptest.NewTLSServer(handler) - ctx.ts = ts +func setupTestServer(handler http.HandlerFunc) (*httptest.Server, *httptest.Server) { + ts1 := httptest.NewTLSServer(handler) + ctx.ts1 = ts1 - _url, err := url.Parse(ts.URL) + _url1, err := url.Parse(ts1.URL) if err != nil { panic(err) } - ctx.oldHost = parseHost - ctx.oldHttpClient = defaultClient.httpClient + ctx.oldHost1 = apps["app_id"].parseHost + ctx.oldHttpClient1 = apps["app_id"].httpClient + apps["app_id"].parseHost = _url1.Host + apps["app_id"].httpClient = &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + }, + } + + ts2 := httptest.NewTLSServer(handler) + ctx.ts2 = ts2 - parseHost = _url.Host - defaultClient.httpClient = &http.Client{ + _url2, err := url.Parse(ts2.URL) + if err != nil { + panic(err) + } + + ctx.oldHost2 = apps["app_id_2"].parseHost + ctx.oldHttpClient2 = apps["app_id_2"].httpClient + apps["app_id_2"].parseHost = _url2.Host + apps["app_id_2"].httpClient = &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, @@ -38,16 +60,24 @@ func setupTestServer(handler http.HandlerFunc) *httptest.Server { }, } - return ts + return ts1, ts2 } func teardownTestServer() { - ctx.ts.Close() - parseHost = ctx.oldHost - defaultClient.httpClient = ctx.oldHttpClient + ctx.ts1.Close() + ctx.ts2.Close() + + apps["app_id"].parseHost = ctx.oldHost1 + apps["app_id"].httpClient = ctx.oldHttpClient1 + apps["app_id_2"].parseHost = ctx.oldHost2 + apps["app_id_2"].httpClient = ctx.oldHttpClient2 } func TestMain(m *testing.M) { Initialize("app_id", "rest_key", "master_key") + ServerURL("https://api.parse.com/1/") + + Initialize("app_id_2", "rest_key_2", "master_key_2") + ServerURL("https://api.parse.com/2/") os.Exit(m.Run()) } diff --git a/create.go b/create.go index e710777..b0b22c7 100644 --- a/create.go +++ b/create.go @@ -24,8 +24,8 @@ func (c *createT) method() string { func (c *createT) endpoint() (string, error) { p := getEndpointBase(c.v) u := url.URL{} - u.Scheme = ParseScheme - u.Host = parseHost + u.Scheme = apps[selectedAppId].parseScheme + u.Host = apps[selectedAppId].parseHost u.Path = p return u.String(), nil @@ -109,7 +109,7 @@ func Signup(username string, password string, user interface{}) error { username: username, password: password, } - if b, err := defaultClient.doRequest(cr); err != nil { + if b, err := apps[selectedAppId].doRequest(cr); err != nil { return err } else { return handleResponse(b, user) @@ -127,7 +127,7 @@ func create(v interface{}, useMasterKey bool, currentSession *sessionT) error { shouldUseMasterKey: useMasterKey, currentSession: currentSession, } - if b, err := defaultClient.doRequest(cr); err != nil { + if b, err := apps[selectedAppId].doRequest(cr); err != nil { return err } else { return handleResponse(b, v) diff --git a/create_test.go b/create_test.go index 3a3f885..469836a 100644 --- a/create_test.go +++ b/create_test.go @@ -115,6 +115,53 @@ func TestCreate(t *testing.T) { } } +func TestCreateOnNonDefaultInitializedServer(t *testing.T) { + setupTestServer(func(w http.ResponseWriter, r *http.Request) { + if h := r.Header.Get(AppIdHeader); h != "app_id_2" { + t.Errorf("request did not have App ID header set!") + } + + if h := r.Header.Get(RestKeyHeader); h != "rest_key_2" { + t.Errorf("request did not have Rest Key header set!") + } + + if h := r.Header.Get(SessionTokenHeader); h != "" { + t.Errorf("request had Session Token header set!") + } + + if h := r.Header.Get(MasterKeyHeader); h != "" { + t.Errorf("request had Master Key header set!") + } + + fmt.Fprintf(w, `{"createdAt":"2021-12-19T18:05:57Z","objectId":"abcDEFG"}`) + }) + defer teardownTestServer() + + u := TestUser{ + FirstName: "Kyle", + LastName: "M", + Email: "kylemcc@gmail.com", + FCount: 11, + } + + AppConnectionWrapper("app_id_2", func() { + err := Create(&u, false) + + if err != nil { + t.Errorf("Unexpected error creating object: %v\n", err) + t.FailNow() + } + + if u.Id != "abcDEFG" { + t.Errorf("Create did not set proper id on instance. u.Id: %v\n", u.Id) + } + + if u.CreatedAt != time.Date(2021, 12, 19, 18, 5, 57, 0, time.UTC) { + t.Errorf("Create did not set proper createdAt date. u.CreatedAt: %v\n", u.CreatedAt) + } + }) +} + func TestCreateUseMasterKey(t *testing.T) { setupTestServer(func(w http.ResponseWriter, r *http.Request) { if h := r.Header.Get(AppIdHeader); h != "app_id" { diff --git a/delete.go b/delete.go index b1e9522..d2bc08f 100644 --- a/delete.go +++ b/delete.go @@ -20,7 +20,7 @@ func _delete(v interface{}, useMasterKey bool, currentSession *sessionT) error { return errors.New("v must be a non-nil pointer") } - _, err := defaultClient.doRequest(&deleteT{inst: v, shouldUseMasterKey: useMasterKey, currentSession: currentSession}) + _, err := apps[selectedAppId].doRequest(&deleteT{inst: v, shouldUseMasterKey: useMasterKey, currentSession: currentSession}) return err } @@ -50,8 +50,8 @@ func (d *deleteT) endpoint() (string, error) { p := getEndpointBase(d.inst) u := url.URL{} - u.Scheme = ParseScheme - u.Host = parseHost + u.Scheme = apps[selectedAppId].parseScheme + u.Host = apps[selectedAppId].parseHost u.Path = path.Join(p, id) return u.String(), nil diff --git a/delete_test.go b/delete_test.go index 814c8bf..afc6ccf 100644 --- a/delete_test.go +++ b/delete_test.go @@ -83,3 +83,42 @@ func TestDelete(t *testing.T) { u := User{Base: Base{Id: "abc"}} Delete(&u, false) } + +func TestDeleteOnNonDefaultInitializedServer(t *testing.T) { + shouldHaveMasterKey := false + setupTestServer(func(w http.ResponseWriter, r *http.Request) { + if h := r.Header.Get(AppIdHeader); h != "app_id_2" { + t.Errorf("request did not have App ID header set!") + } + + if h := r.Header.Get(SessionTokenHeader); h != "" { + t.Errorf("request had Session Token header set!") + } + + if shouldHaveMasterKey { + if h := r.Header.Get(RestKeyHeader); h != "" { + t.Errorf("request had Rest Key header set!") + } + + if h := r.Header.Get(MasterKeyHeader); h != "master_key_2" { + t.Errorf("request did not have Master Key header set!") + } + } else { + if h := r.Header.Get(RestKeyHeader); h != "rest_key_2" { + t.Errorf("request did not have Rest Key header set!") + } + + if h := r.Header.Get(MasterKeyHeader); h != "" { + t.Errorf("request had Master Key header set!") + } + } + + fmt.Fprintf(w, "") + }) + defer teardownTestServer() + + u := User{Base: Base{Id: "abc"}} + AppConnectionWrapper("app_id_2", func() { + Delete(&u, false) + }) +} diff --git a/function.go b/function.go index 36c3830..d393d29 100644 --- a/function.go +++ b/function.go @@ -25,10 +25,10 @@ func (c *callFnT) method() string { } func (c *callFnT) endpoint() (string, error) { - p := path.Join(ParsePath, "functions", c.name) + p := path.Join(apps[selectedAppId].parsePath, "functions", c.name) u := url.URL{} - u.Scheme = ParseScheme - u.Host = parseHost + u.Scheme = apps[selectedAppId].parseScheme + u.Host = apps[selectedAppId].parseHost u.Path = p return u.String(), nil @@ -70,7 +70,7 @@ func callFn(name string, params Params, resp interface{}, currentSession *sessio params: params, currentSession: currentSession, } - if b, err := defaultClient.doRequest(cr); err != nil { + if b, err := apps[selectedAppId].doRequest(cr); err != nil { return err } else { r := fnRespT{} diff --git a/push.go b/push.go index 27e23f8..652ef8a 100644 --- a/push.go +++ b/push.go @@ -54,9 +54,9 @@ func (p *pushT) method() string { func (p *pushT) endpoint() (string, error) { u := url.URL{} - u.Scheme = ParseScheme - u.Host = parseHost - u.Path = "/" + ParsePath + "/push" + u.Scheme = apps[selectedAppId].parseScheme + u.Host = apps[selectedAppId].parseHost + u.Path = "/" + apps[selectedAppId].parsePath + "/push" return u.String(), nil } @@ -144,7 +144,7 @@ func (p *pushT) Data(d map[string]interface{}) PushNotification { } func (p *pushT) Send() error { - b, err := defaultClient.doRequest(p) + b, err := apps[selectedAppId].doRequest(p) data := map[string]interface{}{} if err := json.Unmarshal(b, &data); err != nil { return err diff --git a/query.go b/query.go index f814dde..3643a87 100644 --- a/query.go +++ b/query.go @@ -264,7 +264,7 @@ func (q *queryT) UseMasterKey() Query { func (q *queryT) Get(id string) error { q.op = otGet q.instId = &id - if body, err := defaultClient.doRequest(q); err != nil { + if body, err := apps[selectedAppId].doRequest(q); err != nil { return err } else { return handleResponse(body, q.inst) @@ -785,7 +785,7 @@ func (q *queryT) Each(rc interface{}) (*Iterator, error) { s.Elem().Set(reflect.MakeSlice(sliceType, 0, 100)) // TODO: handle errors and retry if possible - b, err := defaultClient.doRequest(q) + b, err := apps[selectedAppId].doRequest(q) if err != nil { i.err = err i.resChan <- err @@ -836,7 +836,7 @@ func (q *queryT) SetBatchSize(size uint) Query { func (q *queryT) Find() error { q.op = otQuery - if b, err := defaultClient.doRequest(q); err != nil { + if b, err := apps[selectedAppId].doRequest(q); err != nil { return err } else { return handleResponse(b, q.inst) @@ -855,7 +855,7 @@ func (q *queryT) First() error { dv := reflect.New(reflect.SliceOf(rvi.Type())) dv.Elem().Set(reflect.MakeSlice(reflect.SliceOf(rvi.Type()), 0, 1)) - if b, err := defaultClient.doRequest(q); err != nil { + if b, err := apps[selectedAppId].doRequest(q); err != nil { return err } else if err := handleResponse(b, dv.Interface()); err != nil { return err @@ -866,7 +866,7 @@ func (q *queryT) First() error { rv.Elem().Set(dv.Elem().Index(0)) } } else if rvi.Kind() == reflect.Slice { - if b, err := defaultClient.doRequest(q); err != nil { + if b, err := apps[selectedAppId].doRequest(q); err != nil { return err } else if err := handleResponse(b, q.inst); err != nil { return err @@ -884,7 +884,7 @@ func (q *queryT) Count() (int64, error) { q.count = &c var count int64 - if b, err := defaultClient.doRequest(q); err != nil { + if b, err := apps[selectedAppId].doRequest(q); err != nil { return 0, err } else { err := handleResponse(b, &count) @@ -959,8 +959,8 @@ func (q *queryT) endpoint() (string, error) { return "", err } - u.Scheme = ParseScheme - u.Host = parseHost + u.Scheme = apps[selectedAppId].parseScheme + u.Host = apps[selectedAppId].parseHost u.RawQuery = qs u.Path = p diff --git a/query_test.go b/query_test.go index 80ecb40..e15944d 100644 --- a/query_test.go +++ b/query_test.go @@ -523,6 +523,32 @@ func TestCount(t *testing.T) { } } +func TestCountOnNonDefaultInitializedServer(t *testing.T) { + setupTestServer(func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + fmt.Fprintf(w, `{"results":[],"count":73}`) + }) + defer teardownTestServer() + + AppConnectionWrapper("app_id_2", func() { + q, err := NewQuery(&User{}) + if err != nil { + t.Errorf("Unexpected error creating query: %v\n", err) + t.FailNow() + } + + q.EqualTo("city", "Chicago") + cnt, err := q.Count() + if err != nil { + t.Errorf("Error running query: %v\n", err) + } + + if cnt != 73 { + t.Errorf("Count returned incorrect value. Got [%d] expected [%d]\n", cnt, 73) + } + }) +} + func TestFind(t *testing.T) { setupTestServer(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, `{"results":[{"objectId": "123", "createdAt":"2012-04-14T19:23:10.123Z"},{"objectId":"abc","createdAt":"2012-04-14T19:23:10.123Z"}]}`) diff --git a/rest.go b/rest.go index 5b47e73..71d485d 100644 --- a/rest.go +++ b/rest.go @@ -22,12 +22,12 @@ const ( UserAgentHeader = "User-Agent" ) -var ParseScheme string = "https" -var ParsePath string = "1" -var parseHost string = "api.parse.com" +var defaultAppId string = "" +var selectedAppId string = "" var fieldNameCache map[reflect.Type]map[string]string = make(map[reflect.Type]map[string]string) var fieldCache = make(map[reflect.Type]reflect.StructField) +var apps = make(map[string]*appT) type requestT interface { method() string @@ -61,7 +61,7 @@ func (e *parseErrorT) Message() string { return e.ErrorMessage } -type clientT struct { +type appT struct { appId string restKey string masterKey string @@ -70,19 +70,39 @@ type clientT struct { httpClient *http.Client limiter limiter + + parseHost string + parsePath string + parseScheme string } -var defaultClient *clientT +func AppConnectionWrapper(appId string, f func()) error { + if _, found := apps[appId]; !found { + return errors.New("app id is not found") + } + + selectedAppId = appId + f() + selectedAppId = defaultAppId + return nil +} // Initialize the parse library with your API keys func Initialize(appId, restKey, masterKey string) { - defaultClient = &clientT{ + + newApp := &appT{ appId: appId, restKey: restKey, masterKey: masterKey, userAgent: "github.com/kylemcc/parse", httpClient: &http.Client{}, } + + if defaultAppId == "" { + defaultAppId = appId + } + selectedAppId = appId + apps[appId] = newApp } func ServerURL(u string) { @@ -90,20 +110,23 @@ func ServerURL(u string) { if err != nil { panic(err) } - parseHost = url.Host - ParsePath = url.Path - ParseScheme = url.Scheme + apps[selectedAppId].parseHost = url.Host + apps[selectedAppId].parsePath = url.Path + apps[selectedAppId].parseScheme = url.Scheme + selectedAppId = defaultAppId } // Set the timeout for requests to Parse // // Returns an error if called before parse.Initialize func SetHTTPTimeout(t time.Duration) error { - if defaultClient == nil { + if len(apps) == 0 { return errors.New("parse.Initialize must be called before parse.SetHTTPTimeout") } - defaultClient.httpClient.Timeout = t + for _, app := range apps { + app.httpClient.Timeout = t + } return nil } @@ -111,11 +134,13 @@ func SetHTTPTimeout(t time.Duration) error { // // Returns an error if called before parse.Initialize func SetUserAgent(ua string) error { - if defaultClient == nil { + if len(apps) == 0 { return errors.New("parse.Initialize must be called before parse.SetUserAgent") } - defaultClient.userAgent = ua + for _, app := range apps { + app.userAgent = ua + } return nil } @@ -128,24 +153,28 @@ func SetUserAgent(ua string) error { // a maximum number of requests per second. Requests exceeding this limit // will block for the appropriate period of time. func SetRateLimit(limit, burst uint) error { - if defaultClient == nil { - return errors.New("parse.Initialize must be called before parse.SetHTTPTimeout") + if len(apps) == 0 { + return errors.New("parse.Initialize must be called before parse.SetRateLimit") } - defaultClient.limiter = newRateLimiter(limit, burst) + for _, app := range apps { + app.limiter = newRateLimiter(limit, burst) + } return nil } func SetHTTPClient(c *http.Client) error { - if defaultClient == nil { - return errors.New("parse.Initialize must be called before parse.SetHTTPTimeout") + if len(apps) == 0 { + return errors.New("parse.Initialize must be called before parse.SetHTTPClient") } - defaultClient.httpClient = c + for _, app := range apps { + app.httpClient = c + } return nil } -func (c *clientT) doRequest(op requestT) ([]byte, error) { +func (c *appT) doRequest(op requestT) ([]byte, error) { ep, err := op.endpoint() if err != nil { return nil, err @@ -166,8 +195,8 @@ func (c *clientT) doRequest(op requestT) ([]byte, error) { return nil, err } - req.Header.Add(UserAgentHeader, defaultClient.userAgent) - req.Header.Add(AppIdHeader, defaultClient.appId) + req.Header.Add(UserAgentHeader, c.userAgent) + req.Header.Add(AppIdHeader, c.appId) if op.useMasterKey() && c.masterKey != "" && op.session() == nil { req.Header.Add(MasterKeyHeader, c.masterKey) } else { @@ -186,7 +215,7 @@ func (c *clientT) doRequest(op requestT) ([]byte, error) { c.limiter.limit() } - resp, err := defaultClient.httpClient.Do(req) + resp, err := c.httpClient.Do(req) if err != nil { return nil, err } diff --git a/session.go b/session.go index b7cb81b..74b6ec9 100644 --- a/session.go +++ b/session.go @@ -45,7 +45,7 @@ func Login(username, password string, u interface{}) (Session, error) { } s := &sessionT{user: user} - if b, err := defaultClient.doRequest(&loginRequestT{username: username, password: password}); err != nil { + if b, err := apps[selectedAppId].doRequest(&loginRequestT{username: username, password: password}); err != nil { return nil, err } else if st, err := handleLoginResponse(b, s.user); err != nil { return nil, err @@ -68,7 +68,7 @@ func LoginFacebook(authData *FacebookAuthData, u interface{}) (Session, error) { } s := &sessionT{user: user} - if b, err := defaultClient.doRequest(&loginRequestT{authdata: &AuthData{Facebook: authData}}); err != nil { + if b, err := apps[selectedAppId].doRequest(&loginRequestT{authdata: &AuthData{Facebook: authData}}); err != nil { return nil, err } else if st, err := handleLoginResponse(b, s.user); err != nil { return nil, err @@ -102,7 +102,7 @@ func Become(st string, u interface{}) (Session, error) { }, } - if b, err := defaultClient.doRequest(r); err != nil { + if b, err := apps[selectedAppId].doRequest(r); err != nil { return nil, err } else if err := handleResponse(b, r.s.user); err != nil { return nil, err @@ -156,14 +156,14 @@ func (s *loginRequestT) method() string { func (s *loginRequestT) endpoint() (string, error) { u := url.URL{} - u.Scheme = ParseScheme - u.Host = parseHost + u.Scheme = apps[selectedAppId].parseScheme + u.Host = apps[selectedAppId].parseHost if s.s != nil { - u.Path = "/" + ParsePath + "/users/me" + u.Path = "/" + apps[selectedAppId].parsePath + "/users/me" } else if s.authdata != nil { - u.Path = "/" + ParsePath + "/users" + u.Path = "/" + apps[selectedAppId].parsePath + "/users" } else { - u.Path = "/" + ParsePath + "/login" + u.Path = "/" + apps[selectedAppId].parsePath + "/login" } if s.username != "" && s.password != "" { diff --git a/types.go b/types.go index 08543ad..25a1323 100644 --- a/types.go +++ b/types.go @@ -8,6 +8,7 @@ import ( "net/url" "path" "reflect" + "strings" "time" ) @@ -540,7 +541,11 @@ func getEndpointBase(v interface{}) string { p = path.Join("classes", cname) } - p = path.Join(ParsePath, p) + if strings.HasPrefix(apps[selectedAppId].parsePath, "/") { + p = path.Join(apps[selectedAppId].parsePath[1:], p) + } else { + p = path.Join(apps[selectedAppId].parsePath, p) + } return p } @@ -748,9 +753,9 @@ func (c *configRequestT) method() string { func (c *configRequestT) endpoint() (string, error) { u := url.URL{} - u.Scheme = ParseScheme - u.Host = parseHost - u.Path = path.Join(ParsePath, "config") + u.Scheme = apps[selectedAppId].parseScheme + u.Host = apps[selectedAppId].parseHost + u.Path = path.Join(apps[selectedAppId].parsePath, "config") return u.String(), nil } @@ -771,7 +776,7 @@ func (c *configRequestT) contentType() string { } func GetConfig() (Config, error) { - b, err := defaultClient.doRequest(&configRequestT{}) + b, err := apps[selectedAppId].doRequest(&configRequestT{}) if err != nil { return nil, err } diff --git a/update.go b/update.go index 8bbd7a3..798d866 100644 --- a/update.go +++ b/update.go @@ -250,7 +250,7 @@ func (u *updateT) Execute() (err error) { } } } - if b, err := defaultClient.doRequest(u); err != nil { + if b, err := apps[selectedAppId].doRequest(u); err != nil { return err } else { return handleResponse(b, u.inst) @@ -282,8 +282,8 @@ func (u *updateT) endpoint() (string, error) { return "", fmt.Errorf("can not update value - type has no Id field") } - _url.Scheme = ParseScheme - _url.Host = parseHost + _url.Scheme = apps[selectedAppId].parseScheme + _url.Host = apps[selectedAppId].parseHost _url.Path = p return _url.String(), nil