http public request
This commit is contained in:
parent
68677f423e
commit
19b02e129b
|
@ -0,0 +1,8 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/bybit.go.api.iml" filepath="$PROJECT_DIR$/.idea/bybit.go.api.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,186 @@
|
|||
package bybit
|
||||
|
||||
import (
|
||||
"bybit.go.api/handlers"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TimeInForceType define time in force type of order
|
||||
type TimeInForceType string
|
||||
|
||||
// Client define API client
|
||||
type Client struct {
|
||||
APIKey string
|
||||
SecretKey string
|
||||
BaseURL string
|
||||
HTTPClient *http.Client
|
||||
Debug bool
|
||||
Logger *log.Logger
|
||||
do doFunc
|
||||
}
|
||||
|
||||
type doFunc func(req *http.Request) (*http.Response, error)
|
||||
|
||||
// Globals
|
||||
const (
|
||||
timestampKey = "X-BAPI-TIMESTAMP"
|
||||
signatureKey = "X-BAPI-SIGN"
|
||||
apiRequestKey = "X-BAPI-API-KEY"
|
||||
recvWindowKey = "X-BAPI-RECV-WINDOW"
|
||||
signTypeKey = "2"
|
||||
)
|
||||
|
||||
func currentTimestamp() int64 {
|
||||
return FormatTimestamp(time.Now())
|
||||
}
|
||||
|
||||
// FormatTimestamp formats a time into Unix timestamp in milliseconds, as requested by Bybit.
|
||||
func FormatTimestamp(t time.Time) int64 {
|
||||
return t.UnixNano() / int64(time.Millisecond)
|
||||
}
|
||||
|
||||
func PrettyPrint(i interface{}) string {
|
||||
s, _ := json.MarshalIndent(i, "", "\t")
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (c *Client) debug(format string, v ...interface{}) {
|
||||
if c.Debug {
|
||||
c.Logger.Printf(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
// NewClient Create client function for initialising new Bybit client
|
||||
func NewClient(apiKey string, secretKey string, baseURL ...string) *Client {
|
||||
url := "https://api.bybit.com"
|
||||
|
||||
if len(baseURL) > 0 {
|
||||
url = baseURL[0]
|
||||
}
|
||||
|
||||
return &Client{
|
||||
APIKey: apiKey,
|
||||
SecretKey: secretKey,
|
||||
BaseURL: url,
|
||||
HTTPClient: http.DefaultClient,
|
||||
Logger: log.New(os.Stderr, Name, log.LstdFlags),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) parseRequest(r *request, opts ...RequestOption) (err error) {
|
||||
// set request options from user
|
||||
for _, opt := range opts {
|
||||
opt(r)
|
||||
}
|
||||
err = r.validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fullURL := fmt.Sprintf("%s%s", c.BaseURL, r.endpoint)
|
||||
|
||||
queryString := r.query.Encode()
|
||||
body := &bytes.Buffer{}
|
||||
bodyString := r.form.Encode()
|
||||
header := http.Header{}
|
||||
if r.header != nil {
|
||||
header = r.header.Clone()
|
||||
}
|
||||
header.Set("User-Agent", fmt.Sprintf("%s/%s", Name, Version))
|
||||
if bodyString != "" {
|
||||
header.Set("Content-Type", "application/json")
|
||||
body = bytes.NewBufferString(bodyString)
|
||||
}
|
||||
if r.secType == secTypeSigned {
|
||||
header.Set(signTypeKey, "2")
|
||||
header.Set(apiRequestKey, c.APIKey)
|
||||
header.Set(timestampKey, fmt.Sprintf("%d", currentTimestamp()))
|
||||
if r.recvWindow == "" {
|
||||
header.Set(recvWindowKey, "5000")
|
||||
} else {
|
||||
header.Set(recvWindowKey, r.recvWindow)
|
||||
}
|
||||
|
||||
var signatureBase string
|
||||
if r.method == "GET" {
|
||||
signatureBase = fmt.Sprintf("%d%s%s%s", FormatTimestamp(time.Now()), c.APIKey, r.recvWindow, queryString)
|
||||
} else if r.method == "POST" {
|
||||
signatureBase = fmt.Sprintf("%d%s%s%s", FormatTimestamp(time.Now()), c.APIKey, r.recvWindow, bodyString)
|
||||
}
|
||||
|
||||
mac := hmac.New(sha256.New, []byte(c.SecretKey))
|
||||
_, err = mac.Write([]byte(signatureBase))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signatureValue := fmt.Sprintf("%x", mac.Sum(nil))
|
||||
header.Set(signatureKey, signatureValue)
|
||||
}
|
||||
if queryString != "" {
|
||||
fullURL = fmt.Sprintf("%s?%s", fullURL, queryString)
|
||||
}
|
||||
// c.debug("full url: %s, body: %s", fullURL, bodyString)
|
||||
r.fullURL = fullURL
|
||||
r.header = header
|
||||
r.body = body
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) callAPI(ctx context.Context, r *request, opts ...RequestOption) (data []byte, err error) {
|
||||
err = c.parseRequest(r, opts...)
|
||||
req, err := http.NewRequest(r.method, r.fullURL, r.body)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
req.Header = r.header
|
||||
// c.debug("request: %#v", req)
|
||||
f := c.do
|
||||
if f == nil {
|
||||
f = c.HTTPClient.Do
|
||||
}
|
||||
res, err := f(req)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
data, err = io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
defer func() {
|
||||
cerr := res.Body.Close()
|
||||
// Only overwrite the returned error if the original error was nil and an
|
||||
// error occurred while closing the body.
|
||||
if err == nil && cerr != nil {
|
||||
err = cerr
|
||||
}
|
||||
}()
|
||||
// c.debug("response: %#v", res)
|
||||
c.debug("response body: %s", string(data))
|
||||
// c.debug("response status code: %d", res.StatusCode)
|
||||
|
||||
if res.StatusCode >= http.StatusBadRequest {
|
||||
apiErr := new(handlers.APIError)
|
||||
e := json.Unmarshal(data, apiErr)
|
||||
if e != nil {
|
||||
// c.debug("failed to unmarshal json: %s", e)
|
||||
}
|
||||
return nil, apiErr
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// NewServerTimeService Market Endpoints
|
||||
func (c *Client) NewServerTimeService() *ServerTime {
|
||||
return &ServerTime{c: c}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package bybit
|
||||
|
||||
const Name = "bybit.api.go"
|
||||
|
||||
const Version = "1.0.0"
|
|
@ -0,0 +1,7 @@
|
|||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
fmt.Println("hello world")
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
bybit "bybit.go.api"
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ServerTime()
|
||||
}
|
||||
|
||||
func ServerTime() {
|
||||
|
||||
client := bybit.NewClient("", "")
|
||||
|
||||
// set to debug mode
|
||||
client.Debug = true
|
||||
|
||||
// NewServerTimeService
|
||||
serverTime := client.NewServerTimeService().Do(context.Background())
|
||||
fmt.Println(bybit.PrettyPrint(serverTime))
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
module bybit.go.api
|
||||
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/bitly/go-simplejson v0.5.1
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
)
|
|
@ -0,0 +1,3 @@
|
|||
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
|
||||
github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
|
@ -0,0 +1,20 @@
|
|||
package handlers
|
||||
|
||||
import "fmt"
|
||||
|
||||
// APIError define API error when response status is 4xx or 5xx
|
||||
type APIError struct {
|
||||
Code int64 `json:"retCode"`
|
||||
Message string `json:"retMsg"`
|
||||
}
|
||||
|
||||
// Error return error code and message
|
||||
func (e APIError) Error() string {
|
||||
return fmt.Sprintf("<APIError> code=%d, msg=%s", e.Code, e.Message)
|
||||
}
|
||||
|
||||
// IsAPIError check if e is an API error
|
||||
func IsAPIError(e error) bool {
|
||||
_, ok := e.(*APIError)
|
||||
return ok
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package bybit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Binance Check Server Time endpoint (GET /v5/market/time)
|
||||
type ServerTime struct {
|
||||
c *Client
|
||||
}
|
||||
|
||||
// Send the request
|
||||
func (s *ServerTime) Do(ctx context.Context, opts ...RequestOption) (res []byte) {
|
||||
r := &request{
|
||||
method: http.MethodGet,
|
||||
endpoint: "/v5/market/time",
|
||||
secType: secTypeNone,
|
||||
}
|
||||
data, _ := s.c.callAPI(ctx, r, opts...)
|
||||
res = data
|
||||
return res
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package bybit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type secType int
|
||||
|
||||
const (
|
||||
secTypeNone secType = iota
|
||||
secTypeSigned // private request
|
||||
)
|
||||
|
||||
type params map[string]interface{}
|
||||
|
||||
// request define an API request
|
||||
type request struct {
|
||||
method string
|
||||
endpoint string
|
||||
query url.Values
|
||||
form url.Values
|
||||
recvWindow string
|
||||
secType secType
|
||||
header http.Header
|
||||
body io.Reader
|
||||
fullURL string
|
||||
}
|
||||
|
||||
// addParam add param with key/value to query string
|
||||
func (r *request) addParam(key string, value interface{}) *request {
|
||||
if r.query == nil {
|
||||
r.query = url.Values{}
|
||||
}
|
||||
r.query.Add(key, fmt.Sprintf("%v", value))
|
||||
return r
|
||||
}
|
||||
|
||||
// setParam set param with key/value to query string
|
||||
func (r *request) setParam(key string, value interface{}) *request {
|
||||
if r.query == nil {
|
||||
r.query = url.Values{}
|
||||
}
|
||||
r.query.Set(key, fmt.Sprintf("%v", value))
|
||||
return r
|
||||
}
|
||||
|
||||
// setParams set params with key/values to query string
|
||||
func (r *request) setParams(m params) *request {
|
||||
for k, v := range m {
|
||||
r.setParam(k, v)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *request) validate() (err error) {
|
||||
if r.query == nil {
|
||||
r.query = url.Values{}
|
||||
}
|
||||
if r.form == nil {
|
||||
r.form = url.Values{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Append `WithRecvWindow(insert_recvwindow)` to request to modify the default recvWindow value
|
||||
func WithRecvWindow(recvWindow string) RequestOption {
|
||||
return func(r *request) {
|
||||
r.recvWindow = recvWindow
|
||||
}
|
||||
}
|
||||
|
||||
// RequestOption define option type for request
|
||||
type RequestOption func(*request)
|
Loading…
Reference in New Issue