Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Tim Schindler committed May 14, 2014
1 parent df20ef5 commit c0d11b7
Show file tree
Hide file tree
Showing 5 changed files with 309 additions and 0 deletions.
60 changes: 60 additions & 0 deletions client.go
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
}
39 changes: 39 additions & 0 deletions error.go
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,
}
}
61 changes: 61 additions & 0 deletions request.go
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
}
53 changes: 53 additions & 0 deletions util.go
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)
}
96 changes: 96 additions & 0 deletions v1_method.go
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
}

0 comments on commit c0d11b7

Please sign in to comment.