forked from DanielHeckrath/pushd-client-go
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Tim Schindler
committed
May 14, 2014
1 parent
df20ef5
commit c0d11b7
Showing
5 changed files
with
309 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package client | ||
|
||
import ( | ||
"github.com/streadway/handy/breaker" | ||
"net/http" | ||
) | ||
|
||
const ( | ||
UNEXPECTED_ERROR = "Unexpected response from pushd service: status=%d, body=%s" | ||
) | ||
|
||
type Request struct { | ||
circuitBreaker breaker.Breaker | ||
client http.Client | ||
endpoint string | ||
} | ||
|
||
// Impact client version one. | ||
type V1 struct { | ||
request *Request | ||
} | ||
|
||
// Client object holding versioned pushd clients. | ||
type Client struct { | ||
V1 V1 | ||
} | ||
|
||
func NewHttpClient(endpoint string) *Client { | ||
// The circuitbreaker defaults to the following (currently not-configurable values) | ||
// (See https://github.com/streadway/handy/issues/10) | ||
// Window = 5 Seconds | ||
// CoolDown = 1 Second | ||
// MinObservations = 10 | ||
failureRate := 0.5 | ||
|
||
// NOTE: A circuitbreaker is _open_, when too many errors occured and _closed_ when operations can be performed. | ||
// => The breaker will not _open_, if there are less then 10 {MinObservations} values to judge on (even if 100% failed) | ||
// => If 50% {failureRatio} of all requests in the last 5 seconds {window} fails, the breaker will _open_ | ||
// => After 1 second {coolDown}, the breaker will allow one request again, to check availability | ||
|
||
circuitBreaker := breaker.NewBreaker(failureRate) | ||
endpoint = parseEndpoint(endpoint) | ||
next := http.DefaultTransport | ||
transport := breaker.Transport(circuitBreaker, breaker.DefaultResponseValidator, next) | ||
client := http.Client{Transport: transport} | ||
|
||
request := &Request{ | ||
circuitBreaker: circuitBreaker, | ||
endpoint: endpoint, | ||
client: client, | ||
} | ||
|
||
return &Client{ | ||
V1: V1{request}, | ||
} | ||
} | ||
|
||
func parseEndpoint(endpoint string) string { | ||
return "http://" + endpoint | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package client | ||
|
||
import ( | ||
"fmt" | ||
) | ||
|
||
const ( | ||
UNEXPECTED_ERROR_MESSAGE = "Unexpected response from stats service: status=%d, body=%s" | ||
|
||
INVALID_PARAMETER_ERROR = 40001 | ||
ACCOUNT_NOT_EXISTS_ERROR = 40002 | ||
ACCOUNT_ALREADY_EXISTS_ERROR = 40003 | ||
) | ||
|
||
type ClientError struct { | ||
Body string | ||
Code int | ||
} | ||
|
||
func (this *ClientError) Error() string { | ||
return this.Body | ||
} | ||
|
||
func IsError(err error, code int) bool { | ||
switch e := err.(type) { | ||
|
||
case *ClientError: | ||
return e.Code == code | ||
} | ||
|
||
return false | ||
} | ||
|
||
func newUnexpectedResponseError(code int, body string) error { | ||
return &ClientError{ | ||
Body: fmt.Sprintf(UNEXPECTED_ERROR, code, body), | ||
Code: code, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package client | ||
|
||
import ( | ||
"io" | ||
"io/ioutil" | ||
"net/http" | ||
) | ||
|
||
func (this *Request) get(path string) (int, string, error) { | ||
url := this.endpoint + path | ||
res, err := this.client.Get(url) | ||
if err != nil { | ||
return 0, "", err | ||
} | ||
defer res.Body.Close() | ||
|
||
body, err := ioutil.ReadAll(res.Body) | ||
if err != nil { | ||
return 0, "", err | ||
} | ||
|
||
return res.StatusCode, string(body), nil | ||
} | ||
|
||
func (this *Request) post(path, contentType string, payload io.Reader) (int, string, error) { | ||
url := this.endpoint + path | ||
res, err := this.client.Post(url, contentType, payload) | ||
if err != nil { | ||
return 0, "", err | ||
} | ||
defer res.Body.Close() | ||
|
||
body, err := ioutil.ReadAll(res.Body) | ||
if err != nil { | ||
return 0, "", err | ||
} | ||
|
||
return res.StatusCode, string(body), nil | ||
} | ||
|
||
func (this *Request) del(path string) (int, string, error) { | ||
url := this.endpoint + path | ||
req, err := http.NewRequest("DELETE", url, nil) | ||
|
||
if err != nil { | ||
return 0, "", err | ||
} | ||
|
||
res, err := this.client.Do(req) | ||
if err != nil { | ||
return 0, "", err | ||
} | ||
defer res.Body.Close() | ||
|
||
body, err := ioutil.ReadAll(res.Body) | ||
if err != nil { | ||
return 0, "", err | ||
} | ||
|
||
return res.StatusCode, string(body), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package client | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"io" | ||
"strings" | ||
) | ||
|
||
func createJsonPayload(v interface{}) (io.Reader, error) { | ||
payloadBytes, err := json.Marshal(v) | ||
if err != nil { | ||
return strings.NewReader(""), err | ||
} | ||
|
||
return strings.NewReader(string(payloadBytes)), nil | ||
} | ||
|
||
func newSubscribeDeviceRequestPayload(provider, token, language string) io.Reader { | ||
data := url.Values{} | ||
data.Set("proto", provider) | ||
data.Set("token", token) | ||
data.Set("lang", language) | ||
|
||
return bytes.NewBufferString(data.Encode()) | ||
} | ||
|
||
func newFacebookInviteRequestPayload(userId, invitedFbId string) (io.Reader, error) { | ||
requestPayload := map[string]string{ | ||
"user_id": userId, | ||
"invited_facebook_id": invitedFbId, | ||
} | ||
|
||
return createPayload(requestPayload) | ||
} | ||
|
||
func newFacebookConnectRequestPayload(userId, userFbId string) (io.Reader, error) { | ||
requestPayload := map[string]string{ | ||
"user_id": userId, | ||
"user_facebook_id": userFbId, | ||
} | ||
|
||
return createPayload(requestPayload) | ||
} | ||
|
||
func newGetFriendListsRequestPayload(userId string, depth int) (io.Reader, error) { | ||
requestPayload := map[string]interface{}{ | ||
"user_id": userId, | ||
"depth": depth, | ||
} | ||
|
||
return createPayload(requestPayload) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
package client | ||
|
||
import ( | ||
"encoding/json" | ||
"json" | ||
"net/http" | ||
) | ||
|
||
type SubscribeDeviceResponsePayload struct { | ||
Id string `json:"id"` | ||
Created int `json:"created"` | ||
Updated int `json:"updated"` | ||
Proto string `json:"proto"` | ||
Token string `json:"token"` | ||
Lang string `json:"lang"` | ||
} | ||
|
||
func (this *V1) SubscribeDevice(provider, token, language string) (SubscribeDeviceResponsePayload, error) { | ||
empty := SubscribeDeviceResponsePayload{} | ||
|
||
requestPayload := newSubscribeDeviceRequestPayload(provider, token, language) | ||
|
||
path := "/subscribers" | ||
code, body, postErr := this.request.post(path, "application/x-www-form-urlencoded", requestPayload) | ||
if postErr != nil { | ||
return empty, postErr | ||
} | ||
|
||
if code == http.StatusBadRequest { | ||
return empty, newUnexpectedResponseError(INVALID_PARAMETER_ERROR, body) | ||
} | ||
|
||
if code == http.StatusCreated { | ||
return empty, newUnexpectedResponseError(ACCOUNT_ALREADY_EXISTS_ERROR, body) | ||
} | ||
|
||
if code != http.StatusOK { | ||
return empty, newUnexpectedResponseError(code, body) | ||
} | ||
|
||
var responsePayload SubscribeDeviceResponsePayload | ||
if err := json.Unmarshal([]byte{body}, &responsePayload); err != nil { | ||
return empty, err | ||
} | ||
|
||
return responsePayload, nil | ||
} | ||
|
||
func (this *V1) SubscribeDeviceEvent(deviceId, event string) error { | ||
path := "/subscriber/" + deviceId + "/subscriptions/" + event | ||
code, body, postErr := this.request.post(path, "application/x-www-form-urlencoded", nil) | ||
if postErr != nil { | ||
return ostErr | ||
} | ||
|
||
if code == http.StatusBadRequest { | ||
return newUnexpectedResponseError(INVALID_PARAMETER_ERROR, body) | ||
} | ||
|
||
if code == http.StatusNotFound { | ||
return newUnexpectedResponseError(ACCOUNT_NOT_EXISTS_ERROR, body) | ||
} | ||
|
||
if code == http.StatusNoContent { | ||
return newUnexpectedResponseError(ACCOUNT_ALREADY_EXISTS_ERROR, body) | ||
} | ||
|
||
if code != http.StatusCreated { | ||
return newUnexpectedResponseError(code, body) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// DELETE /subscriber/SUBSCRIBER_ID/subscriptions/EVENT_NAME | ||
func (this *V1) UnsubscribeDevice(deviceId string) error { | ||
path := "/subscriber/" + deviceId | ||
code, body, postErr := this.request.del(path) | ||
if postErr != nil { | ||
return ostErr | ||
} | ||
|
||
if code == http.StatusBadRequest { | ||
return newUnexpectedResponseError(INVALID_PARAMETER_ERROR, body) | ||
} | ||
|
||
if code == http.StatusNotFound { | ||
return newUnexpectedResponseError(ACCOUNT_NOT_EXISTS_ERROR, body) | ||
} | ||
|
||
if code != http.StatusNoContent { | ||
return newUnexpectedResponseError(code, body) | ||
} | ||
|
||
return nil | ||
} |