Skip to content

Commit

Permalink
Added client to send hits to Pirsch and short demo.
Browse files Browse the repository at this point in the history
  • Loading branch information
Kugelschieber committed Dec 4, 2020
1 parent d234d6a commit 0787cab
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 2 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea/
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
# go-sdk
Golang Client SDK for Pirsch.
# Pirsch Golang SDK

[![GoDoc](https://godoc.org/github.com/pirsch-analytics/go-sdk?status.svg)](https://godoc.org/github.com/pirsch-analytics/go-sdk)
<a href="https://discord.gg/fAYm4Cz"><img src="https://img.shields.io/discord/739184135649886288?logo=discord" alt="Chat on Discord"></a>

This is the official Golang client SDK for Pirsch. For details, please check out our [documentation](https://docs.pirsch.io/).

## License

MIT
156 changes: 156 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package pirsch

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"sync"
"time"
)

const (
defaultBaseURL = "https://api.pirsch.io"
authenticationEndpoint = "/api/v1/token"
hitEndpoint = "/api/v1/hit"
)

// Client is a client used to access the Pirsch API.
type Client struct {
baseURL string
clientID string
clientSecret string
hostname string
accessToken string
expiresAt time.Time
m sync.Mutex
}

// ClientConfig is used to configure the Client.
type ClientConfig struct {
// BaseURL is optional and can be used to configure a different host for the API.
// This is usually left empty in production environments.
BaseURL string
}

// NewClient creates a new client for given client ID, client secret, hostname, and optional configuration.
// A new client ID and secret can be generated on the Pirsch dashboard.
// The hostname must match the hostname you configured on the Pirsch dashboard (e.g. example.com).
func NewClient(clientID, clientSecret, hostname string, config *ClientConfig) *Client {
baseURL := defaultBaseURL

if config != nil && config.BaseURL != "" {
baseURL = config.BaseURL
}

return &Client{
baseURL: baseURL,
clientID: clientID,
clientSecret: clientSecret,
hostname: hostname,
}
}

// Hit sends a page hit to Pirsch for given http.Request.
func (client *Client) Hit(r *http.Request) error {
return client.performPost(client.baseURL+hitEndpoint, &Hit{
Hostname: client.hostname,
URL: r.URL.String(),
IP: r.RemoteAddr,
CFConnectingIP: r.Header.Get("CF-Connecting-IP"),
XForwardedFor: r.Header.Get("X-Forwarded-For"),
Forwarded: r.Header.Get("Forwarded"),
XRealIP: r.Header.Get("X-Real-IP"),
UserAgent: r.Header.Get("User-Agent"),
AcceptLanguage: r.Header.Get("Accept-Language"),
Referrer: r.Header.Get("Referrer"),
})
}

func (client *Client) refreshToken() error {
body := struct {
ClientId string `json:"client_id"`
ClientSecret string `json:"client_secret"`
}{
client.clientID,
client.clientSecret,
}
bodyJson, err := json.Marshal(&body)

if err != nil {
return err
}

c := http.Client{}
resp, err := c.Post(client.baseURL+authenticationEndpoint, "application/json", bytes.NewBuffer(bodyJson))

if err != nil {
return err
}

respJson := struct {
AccessToken string `json:"access_token"`
ExpiresAt time.Time `json:"expires_at"`
}{}

decoder := json.NewDecoder(resp.Body)

if err := decoder.Decode(&respJson); err != nil {
return err
}

client.m.Lock()
defer client.m.Unlock()
client.accessToken = respJson.AccessToken
client.expiresAt = respJson.ExpiresAt
return nil
}

func (client *Client) performPost(url string, body interface{}) error {
reqBody, err := json.Marshal(body)

if err != nil {
return err
}

req, err := http.NewRequest("POST", url, bytes.NewReader(reqBody))

if err != nil {
return err
}

client.setRequestHeaders(req)
c := http.Client{}
resp, err := c.Do(req)

if err != nil {
return err
}

// refresh access token and retry on 401
if resp.StatusCode == http.StatusUnauthorized {
if err := client.refreshToken(); err != nil {
return err
}

client.setRequestHeaders(req)
resp, err = c.Do(req)

if err != nil {
return err
}
}

if resp.StatusCode != http.StatusOK {
body, _ := ioutil.ReadAll(resp.Body)
return errors.New(fmt.Sprintf("%s: received status code %d on request: %s", url, resp.StatusCode, string(body)))
}

return nil
}

func (client *Client) setRequestHeaders(req *http.Request) {
req.Header.Set("Authorization", "Bearer "+client.accessToken)
}
38 changes: 38 additions & 0 deletions demo/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package main

import (
"github.com/pirsch-analytics/go-sdk"
"log"
"net/http"
)

const (
// Client ID, secret, and hostname for testing.
// Replace them with your own.
clientID = "i9OulOrSI0b5EBNQbb8vg5zfEg2zhQ5q"
clientSecret = "FgwcclcvpTK75oqmbc6UxmJTXX3iCK8JcqPvz7ozzJzouc39KLEeriXQy45Myq92"
hostname = "first.page"
)

func main() {
log.Println("Visit http://localhost:1414")

// Create a client for Pirsch.
client := pirsch.NewClient(clientID, clientSecret, hostname, &pirsch.ClientConfig{
BaseURL: "http://localhost.com:9999",
})

// Add a handler to serve a page.
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// To analyze traffic, send page hits to Pirsch.
// You can control what gets send to Pirsch, in case the page was not found for example.
if r.URL.Path == "/" {
if err := client.Hit(r); err != nil {
log.Println(err)
}
}

w.Write([]byte("<h1>Hello from Pirsch!</h1>"))
})
http.ListenAndServe(":1414", nil)
}
15 changes: 15 additions & 0 deletions types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package pirsch

// Hit are the parameters to send a page hit to Pirsch.
type Hit struct {
Hostname string
URL string `json:"url"`
IP string `json:"ip"`
CFConnectingIP string `json:"cf_connecting_ip"`
XForwardedFor string `json:"x_forwarded_for"`
Forwarded string `json:"forwarded"`
XRealIP string `json:"x_real_ip"`
UserAgent string `json:"user_agent"`
AcceptLanguage string `json:"accept_language"`
Referrer string `json:"referrer"`
}

0 comments on commit 0787cab

Please sign in to comment.