Step-by-Step Guide: Consuming CubeMaster API with Go (net/http)
This guide walks you through integrating the CubeMaster API's /loads
endpoint using Go's net/http
package. It includes authentication, legacy data integration, and response handling.
To use the CubeMaster API, you need an API key (TokenID) for authentication. Here's how to get started:
- Visit the CubeMaster website: https://cubemaster.net.
- Locate the "Sign In" option (typically found in the top-right corner).
- Fill out the registration form with your details (e.g., name, email, password, company information).
- After signing up, log in to your account dashboard.
- Navigate to the "Settings" - "Integration" section to generate your API key (TokenID).
- Generate an API key. Once generated, you’ll receive a unique
TokenID
(e.g.,abc123xyz789
). Copy this key and store it securely, as it will be used in the HTTP headers of your API requests. - Copy the TokenID and store it securely.
Note: The TokenID will be used in the HTTP headers of your POST request for authentication.
A RESTful API (Representational State Transfer) is a way for applications to communicate over the internet using standard HTTP methods. Here’s a quick breakdown:
-
HTTP Methods:
GET
: Retrieve data.POST
: Send data to create something (used in this guide).PUT
: Update data.DELETE
: Remove data.
- Endpoints: URLs like
https://api.cubemaster.net/loads
define where to send requests. - JSON: A common data format for sending and receiving information (used in this API).
- Headers: Metadata sent with requests, like authentication tokens.
- Status Codes: Responses like
200 OK
(success) or401 Unauthorized
(authentication failed).
In this guide, we’ll use a POST
request to send load data to the CubeMaster API and receive a response with loading details.
Ensure you have Go installed and set up:
- Download and install Go from golang.org/dl/.
- Verify installation:
go version
(e.g.,go1.21.0
). - Create a new project directory:
mkdir cubemaster-api && cd cubemaster-api
. - Initialize a Go module:
go mod init cubemaster-api
. - No external packages are needed since we’re using the standard
net/http
library.
Create a file named main.go
to write your code.
Assume your customer’s legacy database stores order or shipment data (e.g., items, quantities, dimensions). Here’s how to fetch and map it to the API request:
package main
import (
"database/sql"
_ "github.com/go-sql-driver/mysql" // Example: MySQL driver
"log"
)
type Item struct {
Name string
Length float64
Width float64
Height float64
Weight float64
Qty int
Color string
}
func fetchItemsFromDB() ([]Item, error) {
db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/legacy_db")
if err != nil {
return nil, err
}
defer db.Close()
rows, err := db.Query("SELECT name, length, width, height, weight, qty, color FROM shipments")
if err != nil {
return nil, err
}
defer rows.Close()
var items []Item
for rows.Next() {
var item Item
if err := rows.Scan(&item.Name, &item.Length, &item.Width, &item.Height, &item.Weight, &item.Qty, &item.Color); err != nil {
return nil, err
}
items = append(items, item)
}
return items, nil
}
This example uses a MySQL database. Adjust the driver and query based on your legacy system (e.g., PostgreSQL, SQLite). The retrieved data will be used in the API request.
Use net/http
to build the POST request with the TokenID in the header and JSON payload:
package main
import (
"bytes"
"encoding/json"
"net/http"
"os"
)
type Cargo struct {
Name string `json:"Name"`
Length float64 `json:"Length"`
Width float64 `json:"Width"`
Height float64 `json:"Height"`
Weight float64 `json:"Weight"`
OrientationsAllowed string `json:"OrientationsAllowed"`
TurnAllowedOnFloor bool `json:"TurnAllowedOnFloor"`
Qty int `json:"Qty"`
ColorKnownName string `json:"ColorKnownName"`
}
type Container struct {
VehicleType string `json:"VehicleType"`
Name string `json:"Name"`
Length float64 `json:"Length"`
Width float64 `json:"Width"`
Height float64 `json:"Height"`
ColorKnownName string `json:"ColorKnownName"`
}
type Rules struct {
IsWeightLimited bool `json:"IsWeightLimited"`
IsSequenceUsed bool `json:"IsSequenceUsed"`
FillDirection string `json:"FillDirection"`
CalculationType string `json:"CalculationType"`
}
type LoadRequest struct {
Title string `json:"Title"`
Description string `json:"Description"`
Cargoes []Cargo `json:"Cargoes"`
Containers []Container `json:"Containers"`
Rules Rules `json:"Rules"`
}
func buildRequest() (*http.Request, error) {
// Fetch items from legacy database
items, err := fetchItemsFromDB()
if err != nil {
return nil, err
}
// Map database items to Cargoes
var cargoes []Cargo
for _, item := range items {
cargoes = append(cargoes, Cargo{
Name: item.Name,
Length: item.Length,
Width: item.Width,
Height: item.Height,
Weight: item.Weight,
OrientationsAllowed: "OrientationsAll",
TurnAllowedOnFloor: false,
Qty: item.Qty,
ColorKnownName: item.Color,
})
}
// Define the request payload
payload := LoadRequest{
Title: "New Mixed Truck Load",
Description: "Hello Web API",
Cargoes: cargoes,
Containers: []Container{
{
VehicleType: "Dry",
Name: "53FT-Intermodal",
Length: 630,
Width: 98,
Height: 106,
ColorKnownName: "Blue",
},
},
Rules: Rules{
IsWeightLimited: true,
IsSequenceUsed: false,
FillDirection: "FrontToRear",
CalculationType: "MixLoad",
},
}
// Marshal to JSON
jsonData, err := json.Marshal(payload)
if err != nil {
return nil, err
}
// Create the POST request
req, err := http.NewRequest("POST", "https://api.cubemaster.net/loads", bytes.NewBuffer(jsonData))
if err != nil {
return nil, err
}
// Set headers
req.Header.Set("Content-Type", "application/json")
req.Header.Set("TokenID", os.Getenv("CUBEMASTER_TOKENID")) // Load TokenID from environment
return req, nil
}
Request JSON Example:
{
"Title": "New Mixed Truck Load",
"Description": "Hello Web API",
"Cargoes": [
{
"Name": "ITEM001",
"Length": 72,
"Width": 30,
"Height": 75,
"Weight": 1002.45,
"OrientationsAllowed": "OrientationsAll",
"TurnAllowedOnFloor": false,
"Qty": 16,
"ColorKnownName": "Brown"
},
{
"Name": "ITEM002",
"Length": 27.31,
"Width": 37.5,
"Height": 76.67,
"Weight": 521.45,
"OrientationsAllowed": "OrientationsAll",
"TurnAllowedOnFloor": false,
"Qty": 28,
"ColorKnownName": "Aqua"
},
{
"Name": "SKU0005",
"Length": 27.31,
"Width": 9.5,
"Height": 75.67,
"Weight": 501.45,
"OrientationsAllowed": "OrientationsAll",
"TurnAllowedOnFloor": true,
"Qty": 24,
"ColorKnownName": "Beige"
},
{
"Name": "SKU0005",
"Qty": 23
},
{
"Name": "SKU0008",
"Qty": 34
}
],
"Containers": [
{
"VehicleType": "Dry",
"Name": "53FT-Intermodal",
"Length": 630,
"Width": 98,
"Height": 106,
"ColorKnownName": "Blue"
}
],
"Rules": {
"IsWeightLimited": true,
"IsSequenceUsed": false,
"FillDirection": "FrontToRear",
"CalculationType": "MixLoad"
}
}
The TokenID
is added to the header for authentication, and the payload is constructed with data from the legacy database.
Use http.Client
to send the request and process the response:
package main
import (
"encoding/json"
"io/ioutil"
"log"
"net/http"
)
type LoadResponse struct {
Status string `json:"status"`
Message string `json:"message"`
CalculationError string `json:"calculationError"`
Document struct {
Title string `json:"title"`
Description string `json:"description"`
CalculationTimeInSeconds float64 `json:"calculationTimeInSeconds"`
CreatedBy string `json:"createdBy"`
CreatedAt string `json:"createdAt"`
} `json:"document"`
LoadSummary struct {
CargoesLoaded int `json:"cargoesLoaded"`
PiecesLoaded int `json:"piecesLoaded"`
VolumeLoaded float64 `json:"volumeLoaded"`
WeightLoaded float64 `json:"weightLoaded"`
ContainersLoaded int `json:"containersLoaded"`
} `json:"loadSummary"`
FilledContainers []struct {
Name string `json:"name"`
LoadSummary struct {
VolumeUtilization float64 `json:"volumeUtilization"`
WeightLoaded float64 `json:"weightLoaded"`
} `json:"loadSummary"`
Graphics struct {
Images struct {
Path3DDiagram string `json:"path3DDiagram"`
} `json:"images"`
} `json:"graphics"`
} `json:"filledContainers"`
}
func main() {
// Build the request
req, err := buildRequest()
if err != nil {
log.Fatalf("Error building request: %v", err)
}
// Create HTTP client and send request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Fatalf("Error sending request: %v", err)
}
defer resp.Body.Close()
// Check status code
if resp.StatusCode != http.StatusOK {
body, _ := ioutil.ReadAll(resp.Body)
log.Fatalf("Request failed with status %d: %s", resp.StatusCode, string(body))
}
// Read and unmarshal response
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatalf("Error reading response: %v", err)
}
var loadResp LoadResponse
if err := json.Unmarshal(body, &loadResp); err != nil {
log.Fatalf("Error unmarshaling response: %v", err)
}
// Handle the response
log.Printf("Status: %s", loadResp.Status)
log.Printf("Message: %s", loadResp.Message)
if loadResp.CalculationError != "" {
log.Printf("Calculation Error: %s", loadResp.CalculationError)
}
log.Printf("Load Summary: %d cargoes, %d pieces, %.2f volume, %.2f weight",
loadResp.LoadSummary.CargoesLoaded,
loadResp.LoadSummary.PiecesLoaded,
loadResp.LoadSummary.VolumeLoaded,
loadResp.LoadSummary.WeightLoaded)
for _, container := range loadResp.FilledContainers {
log.Printf("Container %s: %.2f%% volume utilization, %.2f weight",
container.Name, container.LoadSummary.VolumeUtilization, container.LoadSummary.WeightLoaded)
log.Printf("3D Diagram: %s", container.Graphics.Images.Path3DDiagram)
}
}
Response JSON Example:
{
"status": "succeed",
"message": "Engine created. 5 cargoes. 1 empty containers. Calculation started. Calculation ended. The load built successfully. The load saved to the cloud database.",
"calculationError": "InvalidCargoSize",
"document": {
"title": "New Mixed Truck Load",
"description": "Hello Web API",
"isShared": true,
"isAutoSaved": true,
"isPending": false,
"calculationTimeInSeconds": 0.6152743,
"processId": "",
"batchId": "",
"createdBy": "CHANG@LOGEN.CO.KR",
"createdAt": "2023-02-11T01:17:01.7392204+09:00",
"updatedAt": "0001-01-01T00:00:00"
},
"loadSummary": {
"cargoesLoaded": 68,
"piecesLoaded": 68,
"cargoesLeft": 0,
"piecesLeft": 57,
"unitloadsLoaded": 0,
"volumeLoaded": 5261723.4606,
"weightLoaded": 42674.59999999999,
"priceLoaded": 0,
"containersLoaded": 1
},
"filledContainers": [
{
"name": "#1 53FT-Intermodal",
"sequence": 1,
"loadSummary": {
"cargoesLoaded": 68,
"piecesLoaded": 68,
"unitloadsLoaded": 0,
"volumeLoaded": 5261723.4606,
"volumeUtilization": 80.39990374424703,
"floorLoaded": 57090.75,
"floorUtilization": 92.46963070942662,
"weightLoaded": 42674.59999999999,
"weightTotal": 42674.59999999999,
"dimWeight": 39424.33734939759,
"priceLoaded": 0,
"cargoesPerLayer": 14,
"layersPerUnitload": 0
},
"graphics": {
"images": {
"path3DDiagram": "https://api.cubemaster.net/runtimes/b28413ca_51ed_44c9_b92e_13147363fd61.PNG",
"pathComposite": "https://api.cubemaster.net/runtimes/7eb09974_2f1d_41bc_9371_2002658dce07.PNG"
}
}
}
]
}
The response is parsed into a struct, and key details (e.g., status, load summary, container utilization) are logged. Adjust the struct fields to capture more data as needed.
To ensure your API integration works smoothly, add monitoring and debugging:
- Logging: Use
log.Printf
to track request/response details (already included in Step 6). - HTTP Debugging: Use
http.Client
with a custom transport to log request/response details: - Error Handling: Check
resp.StatusCode
and log detailed errors (e.g.,401 Unauthorized
if TokenID is invalid). - Monitoring: Add metrics (e.g., request latency, success rate) using a library like
prometheus/client_golang
if needed. - Testing: Use tools like Postman to manually test the API with your TokenID and JSON payload.
import (
"net/http"
"net/http/httputil"
)
func main() {
req, err := buildRequest()
if err != nil {
log.Fatalf("Error building request: %v", err)
}
// Dump request for debugging
dump, _ := httputil.DumpRequestOut(req, true)
log.Printf("Request:\n%s", dump)
// Custom transport for debugging
client := &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
},
}
resp, err := client.Do(req)
if err != nil {
log.Fatalf("Error sending request: %v", err)
}
defer resp.Body.Close()
// Dump response for debugging
dumpResp, _ := httputil.DumpResponse(resp, true)
log.Printf("Response:\n%s", dumpResp)
}
Run your program with go run main.go
and check logs for issues. Set the TokenID environment variable: export CUBEMASTER_TOKENID=your_token_here
.