Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Tests
on:
pull_request:
branches: [master]

push:
branches: [master]

jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: "Test"
steps:
- uses: actions/checkout@v5
- name: Setup go
uses: actions/setup-go@v6
with:
go-version: ${{ matrix.go }}
- name: Run tests
run: go test ./... -v -race -timeout 30s
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bin/
151 changes: 150 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,150 @@
# globalping-go
# Globalping Go API Client

The official Go client for the [Globalping API](https://globalping.io/docs/api.globalping.io).

## Installation

To install the client, run the following command:

```bash
go get github.com/jsdelivr/globalping-go
```

## Usage

To use the client, import it into your Go code:

```go
import "github.com/jsdelivr/globalping-go"

func main() {
client := globalping.NewClient(globalping.Config{
AuthToken: "<your_access_token>", // Optional
})
}
```

### Create Measurement

Creates a new measurement with the set parameters. The measurement runs asynchronously, and you can retrieve its current state using GetMeasurement() or wait for its final state using AwaitMeasurement().

```go
o := &globalping.MeasurementCreate{
Type: globalping.MeasurementTypePing,
Target: "google.com",
Limit: 1,
Locations: []globalping.Locations{
{
Magic: "world",
},
},
}

res, err := client.CreateMeasurement(ctx, o)
if err != nil {
fmt.Println(err)
return
}
```

### Get a measurement

Returns the current state of the measurement.

```go
measurement, err := client.GetMeasurement(ctx, res.ID)
if err != nil {
fmt.Println(err)
return
}

fmt.Printf("%+v\n", measurement)
```

### Await a measurement

Similar to GetMeasurement(), but keeps pooling the API until the measurement is finished, and returns its final state.

```go
measurement, err := client.AwaitMeasurement(ctx, res.ID)
if err != nil {
fmt.Println(err)
return
}

fmt.Printf("%+v\n", measurement)
```

### Get raw measurement bytes

Returns the raw measurement bytes.

```go
b, err := client.GetMeasurementRaw(ctx, res.ID)
if err != nil {
fmt.Println(err)
return
}
```

### Probes

Returns a list of all probes currently online and their metadata, such as location and assigned tags.

```go
probes, err := client.Probes(ctx)
if err != nil {
fmt.Println(err)
return
}

fmt.Printf("%+v\n", probes)
```

### Get rate limits

Returns rate limits for the current user (if authenticated) or IP address (if not authenticated).

```go
limits, err := client.Limits(ctx)
if err != nil {
fmt.Println(err)
return
}

fmt.Printf("%+v\n", limits)
```

### Error handling

API errors are returned as `*globalping.MeasurementError` instances. You can access the error code and headers using the `StatusCode` and `Header` fields.

```go
measurement, err := client.GetMeasurement(ctx, res.ID)
if err != nil {
if measurementErr, ok := err.(*globalping.MeasurementError); ok {
// measurementErr.StatusCode
// measurementErr.Header
} else {
fmt.Println(err)
}
}
```

### Advanced configuration

`AuthToken`

A user authentication token obtained from https://dash.globalping.io or via OAuth (currently available only to official Globalping apps).

`UserAgent`

Refers to this library by default. If you build another open-source project based on this library, you should override this value to point to your project instead.

`CacheExpireSeconds`

Specifies the expiration time for cached measurements in seconds. 0 means no expiration.

`HTTPClient`

Custom HTTP client to use for requests.
77 changes: 77 additions & 0 deletions cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package globalping

import "time"

type cacheEntry struct {
ETag string
Data []byte
ExpireAt int64 // Unix timestamp
}

func (c *client) CacheClean() {
c.cleanupCache()
}

func (c *client) CachePurge() {
c.mu.Lock()
defer c.mu.Unlock()
c.cache = map[string]*cacheEntry{}
}

func (c *client) getETag(id string) string {
c.mu.RLock()
defer c.mu.RUnlock()
e, ok := c.cache[id]
if !ok {
return ""
}
if e.ExpireAt > 0 && e.ExpireAt < time.Now().Unix() {
return ""
}
return e.ETag
}

func (c *client) getCachedResponse(id string) []byte {
c.mu.RLock()
defer c.mu.RUnlock()
e, ok := c.cache[id]
if !ok {
return nil
}
if e.ExpireAt > 0 && e.ExpireAt < time.Now().Unix() {
return nil
}
return e.Data
}

func (c *client) cacheResponse(id string, etag string, resp []byte) {
c.mu.Lock()
defer c.mu.Unlock()
var expires int64
if c.cacheExpireSeconds != 0 {
expires = time.Now().Unix() + c.cacheExpireSeconds
}
e, ok := c.cache[id]
if ok {
e.ETag = etag
e.Data = resp
e.ExpireAt = expires
} else {
c.cache[id] = &cacheEntry{
ETag: etag,
Data: resp,
ExpireAt: expires,
}
}
}

func (c *client) cleanupCache() {
c.mu.Lock()
defer c.mu.Unlock()
now := time.Now().Unix()
for k, v := range c.cache {
if v.ExpireAt > 0 && v.ExpireAt < now {
delete(c.cache, k)
}
}
}
96 changes: 96 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package globalping

import (
"context"
"net/http"
"sync"
"time"
)

var (
APIURL = "https://api.globalping.io/v1"
)

type Client interface {
// Creates a new measurement with parameters set in the request body. The measurement runs asynchronously and you can retrieve its current state at the URL returned in the Location header.
//
// https://globalping.io/docs/api.globalping.io#post-/v1/measurements
CreateMeasurement(ctx context.Context, measurement *MeasurementCreate) (*MeasurementCreateResponse, error)

// Returns the status and results of an existing measurement. Measurements are typically available for up to 7 days after creation.
//
// https://globalping.io/docs/api.globalping.io#get-/v1/measurements/-id-
GetMeasurement(ctx context.Context, id string) (*Measurement, error)

// Waits for the measurement to complete and returns the results.
//
// https://globalping.io/docs/api.globalping.io#get-/v1/measurements/-id-
AwaitMeasurement(ctx context.Context, id string) (*Measurement, error)

// Returns the status and results of an existing measurement. Measurements are typically available for up to 7 days after creation.
//
// https://globalping.io/docs/api.globalping.io#get-/v1/measurements/-id-
GetMeasurementRaw(ctx context.Context, id string) ([]byte, error)

// Returns the rate limits for the current user or IP address.
//
// https://globalping.io/docs/api.globalping.io#get-/v1/limits
Limits(ctx context.Context) (*LimitsResponse, error)

// Returns a list of all probes currently online and their metadata, such as location and assigned tags.
//
// https://globalping.io/docs/api.globalping.io#get-/v1/probes
Probes(ctx context.Context) (*ProbesResponse, error)

// Clears the expired cached entries.
CacheClean()

// Removes all cached entries regardless of expiration.
CachePurge()
}

type Config struct {
AuthToken string // Your GlobalPing API access token. Optional.
UserAgent string // User agent string for API requests. Optional.
CacheExpireSeconds int64 // Cache entry expiration time in seconds. 0 means no expiration.

HTTPClient *http.Client // If set, this client will be used for API requests. Optional.
}

type client struct {
mu sync.RWMutex
http *http.Client
cache map[string]*cacheEntry

cacheExpireSeconds int64
userAgent string
authToken string
}

// Creates a new client with the given configuration.
//
// Note: The client caches API responses.
// Set CacheExpireSeconds to configure the entry expiration and use CacheClean to clear the expired entries when reusing the client.
func NewClient(config Config) Client {
c := &client{
mu: sync.RWMutex{},
userAgent: config.UserAgent,
authToken: config.AuthToken,
cache: map[string]*cacheEntry{},
cacheExpireSeconds: config.CacheExpireSeconds,
}

if config.UserAgent == "" {
c.userAgent = "jsdelivr/globalping-go"
}

if config.HTTPClient != nil {
c.http = config.HTTPClient
} else {
c.http = &http.Client{
Timeout: 30 * time.Second,
}
}

return c
}
17 changes: 17 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module github.com/jsdelivr/globalping-go

go 1.24.0

require (
github.com/andybalholm/brotli v1.2.0
github.com/stretchr/testify v1.11.1
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading